OR-Tools  9.1
cuts.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#include "ortools/sat/cuts.h"
15
16#include <algorithm>
17#include <cmath>
18#include <cstdint>
19#include <functional>
20#include <limits>
21#include <memory>
22#include <string>
23#include <utility>
24#include <vector>
25
30#include "ortools/sat/integer.h"
34#include "ortools/sat/util.h"
36
37namespace operations_research {
38namespace sat {
39
40namespace {
41
42// Minimum amount of violation of the cut constraint by the solution. This
43// is needed to avoid numerical issues and adding cuts with minor effect.
44const double kMinCutViolation = 1e-4;
45
46// Returns the lp value of a Literal.
47double GetLiteralLpValue(
48 const Literal lit,
50 const IntegerEncoder* encoder) {
51 const IntegerVariable direct_view = encoder->GetLiteralView(lit);
52 if (direct_view != kNoIntegerVariable) {
53 return lp_values[direct_view];
54 }
55 const IntegerVariable opposite_view = encoder->GetLiteralView(lit.Negated());
56 DCHECK_NE(opposite_view, kNoIntegerVariable);
57 return 1.0 - lp_values[opposite_view];
58}
59
60// Returns a constraint that disallow all given variables to be at their current
61// upper bound. The arguments must form a non-trival constraint of the form
62// sum terms (coeff * var) <= upper_bound.
63LinearConstraint GenerateKnapsackCutForCover(
64 const std::vector<IntegerVariable>& vars,
65 const std::vector<IntegerValue>& coeffs, const IntegerValue upper_bound,
66 const IntegerTrail& integer_trail) {
67 CHECK_EQ(vars.size(), coeffs.size());
68 CHECK_GT(vars.size(), 0);
69 LinearConstraint cut;
70 IntegerValue cut_upper_bound = IntegerValue(0);
71 IntegerValue max_coeff = coeffs[0];
72 // slack = \sum_{i}(coeffs[i] * upper_bound[i]) - upper_bound.
73 IntegerValue slack = -upper_bound;
74 for (int i = 0; i < vars.size(); ++i) {
75 const IntegerValue var_upper_bound =
76 integer_trail.LevelZeroUpperBound(vars[i]);
77 cut_upper_bound += var_upper_bound;
78 cut.vars.push_back(vars[i]);
79 cut.coeffs.push_back(IntegerValue(1));
80 max_coeff = std::max(max_coeff, coeffs[i]);
81 slack += coeffs[i] * var_upper_bound;
82 }
83 CHECK_GT(slack, 0.0) << "Invalid cover for knapsack cut.";
84 cut_upper_bound -= CeilRatio(slack, max_coeff);
85 cut.lb = kMinIntegerValue;
86 cut.ub = cut_upper_bound;
87 VLOG(2) << "Generated Knapsack Constraint:" << cut.DebugString();
88 return cut;
89}
90
91bool SolutionSatisfiesConstraint(
92 const LinearConstraint& constraint,
94 const double activity = ComputeActivity(constraint, lp_values);
95 const double tolerance = 1e-6;
96 return (activity <= ToDouble(constraint.ub) + tolerance &&
97 activity >= ToDouble(constraint.lb) - tolerance)
98 ? true
99 : false;
100}
101
102bool SmallRangeAndAllCoefficientsMagnitudeAreTheSame(
103 const LinearConstraint& constraint, IntegerTrail* integer_trail) {
104 if (constraint.vars.empty()) return true;
105
106 const int64_t magnitude = std::abs(constraint.coeffs[0].value());
107 for (int i = 1; i < constraint.coeffs.size(); ++i) {
108 const IntegerVariable var = constraint.vars[i];
109 if (integer_trail->LevelZeroUpperBound(var) -
110 integer_trail->LevelZeroLowerBound(var) >
111 1) {
112 return false;
113 }
114 if (std::abs(constraint.coeffs[i].value()) != magnitude) {
115 return false;
116 }
117 }
118 return true;
119}
120
121bool AllVarsTakeIntegerValue(
122 const std::vector<IntegerVariable> vars,
124 for (IntegerVariable var : vars) {
125 if (std::abs(lp_values[var] - std::round(lp_values[var])) > 1e-6) {
126 return false;
127 }
128 }
129 return true;
130}
131
132// Returns smallest cover size for the given constraint taking into account
133// level zero bounds. Smallest Cover size is computed as follows.
134// 1. Compute the upper bound if all variables are shifted to have zero lower
135// bound.
136// 2. Sort all terms (coefficient * shifted upper bound) in non decreasing
137// order.
138// 3. Add terms in cover until term sum is smaller or equal to upper bound.
139// 4. Add the last item which violates the upper bound. This forms the smallest
140// cover. Return the size of this cover.
141int GetSmallestCoverSize(const LinearConstraint& constraint,
142 const IntegerTrail& integer_trail) {
143 IntegerValue ub = constraint.ub;
144 std::vector<IntegerValue> sorted_terms;
145 for (int i = 0; i < constraint.vars.size(); ++i) {
146 const IntegerValue coeff = constraint.coeffs[i];
147 const IntegerVariable var = constraint.vars[i];
148 const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
149 const IntegerValue var_lb = integer_trail.LevelZeroLowerBound(var);
150 ub -= var_lb * coeff;
151 sorted_terms.push_back(coeff * (var_ub - var_lb));
152 }
153 std::sort(sorted_terms.begin(), sorted_terms.end(),
154 std::greater<IntegerValue>());
155 int smallest_cover_size = 0;
156 IntegerValue sorted_term_sum = IntegerValue(0);
157 while (sorted_term_sum <= ub &&
158 smallest_cover_size < constraint.vars.size()) {
159 sorted_term_sum += sorted_terms[smallest_cover_size++];
160 }
161 return smallest_cover_size;
162}
163
164bool ConstraintIsEligibleForLifting(const LinearConstraint& constraint,
165 const IntegerTrail& integer_trail) {
166 for (const IntegerVariable var : constraint.vars) {
167 if (integer_trail.LevelZeroLowerBound(var) != IntegerValue(0) ||
168 integer_trail.LevelZeroUpperBound(var) != IntegerValue(1)) {
169 return false;
170 }
171 }
172 return true;
173}
174} // namespace
175
177 const LinearConstraint& constraint,
179 const std::vector<IntegerValue>& cut_vars_original_coefficients,
180 const IntegerTrail& integer_trail, TimeLimit* time_limit,
181 LinearConstraint* cut) {
182 std::set<IntegerVariable> vars_in_cut;
183 for (IntegerVariable var : cut->vars) {
184 vars_in_cut.insert(var);
185 }
186
187 std::vector<std::pair<IntegerValue, IntegerVariable>> non_zero_vars;
188 std::vector<std::pair<IntegerValue, IntegerVariable>> zero_vars;
189 for (int i = 0; i < constraint.vars.size(); ++i) {
190 const IntegerVariable var = constraint.vars[i];
191 if (integer_trail.LevelZeroLowerBound(var) != IntegerValue(0) ||
192 integer_trail.LevelZeroUpperBound(var) != IntegerValue(1)) {
193 continue;
194 }
195 if (vars_in_cut.find(var) != vars_in_cut.end()) continue;
196 const IntegerValue coeff = constraint.coeffs[i];
197 if (lp_values[var] <= 1e-6) {
198 zero_vars.push_back({coeff, var});
199 } else {
200 non_zero_vars.push_back({coeff, var});
201 }
202 }
203
204 // Decide lifting sequence (nonzeros, zeros in nonincreasing order
205 // of coefficient ).
206 std::sort(non_zero_vars.rbegin(), non_zero_vars.rend());
207 std::sort(zero_vars.rbegin(), zero_vars.rend());
208
209 std::vector<std::pair<IntegerValue, IntegerVariable>> lifting_sequence(
210 std::move(non_zero_vars));
211
212 lifting_sequence.insert(lifting_sequence.end(), zero_vars.begin(),
213 zero_vars.end());
214
215 // Form Knapsack.
216 std::vector<double> lifting_profits;
217 std::vector<double> lifting_weights;
218 for (int i = 0; i < cut->vars.size(); ++i) {
219 lifting_profits.push_back(ToDouble(cut->coeffs[i]));
220 lifting_weights.push_back(ToDouble(cut_vars_original_coefficients[i]));
221 }
222
223 // Lift the cut.
224 bool is_lifted = false;
225 bool is_solution_optimal = false;
226 KnapsackSolverForCuts knapsack_solver("Knapsack cut lifter");
227 for (auto entry : lifting_sequence) {
228 is_solution_optimal = false;
229 const IntegerValue var_original_coeff = entry.first;
230 const IntegerVariable var = entry.second;
231 const IntegerValue lifting_capacity = constraint.ub - entry.first;
232 if (lifting_capacity <= IntegerValue(0)) continue;
233 knapsack_solver.Init(lifting_profits, lifting_weights,
234 ToDouble(lifting_capacity));
235 knapsack_solver.set_node_limit(100);
236 // NOTE: Since all profits and weights are integer, solution of
237 // knapsack is also integer.
238 // TODO(user): Use an integer solver or heuristic.
239 knapsack_solver.Solve(time_limit, &is_solution_optimal);
240 const double knapsack_upper_bound =
241 std::round(knapsack_solver.GetUpperBound());
242 const IntegerValue cut_coeff =
243 cut->ub - static_cast<int64_t>(knapsack_upper_bound);
244 if (cut_coeff > IntegerValue(0)) {
245 is_lifted = true;
246 cut->vars.push_back(var);
247 cut->coeffs.push_back(cut_coeff);
248 lifting_profits.push_back(ToDouble(cut_coeff));
249 lifting_weights.push_back(ToDouble(var_original_coeff));
250 }
251 }
252 return is_lifted;
253}
254
256 const LinearConstraint& constraint,
258 const IntegerTrail& integer_trail) {
259 IntegerValue ub = constraint.ub;
260 LinearConstraint constraint_with_left_vars;
261 for (int i = 0; i < constraint.vars.size(); ++i) {
262 const IntegerVariable var = constraint.vars[i];
263 const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
264 const IntegerValue coeff = constraint.coeffs[i];
265 if (ToDouble(var_ub) - lp_values[var] <= 1.0 - kMinCutViolation) {
266 constraint_with_left_vars.vars.push_back(var);
267 constraint_with_left_vars.coeffs.push_back(coeff);
268 } else {
269 // Variable not in cut
270 const IntegerValue var_lb = integer_trail.LevelZeroLowerBound(var);
271 ub -= coeff * var_lb;
272 }
273 }
274 constraint_with_left_vars.ub = ub;
275 constraint_with_left_vars.lb = constraint.lb;
276 return constraint_with_left_vars;
277}
278
280 const IntegerTrail& integer_trail) {
281 IntegerValue term_sum = IntegerValue(0);
282 for (int i = 0; i < constraint.vars.size(); ++i) {
283 const IntegerVariable var = constraint.vars[i];
284 const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
285 const IntegerValue coeff = constraint.coeffs[i];
286 term_sum += coeff * var_ub;
287 }
288 if (term_sum <= constraint.ub) {
289 VLOG(2) << "Filtered by cover filter";
290 return true;
291 }
292 return false;
293}
294
296 const LinearConstraint& preprocessed_constraint,
298 const IntegerTrail& integer_trail) {
299 std::vector<double> variable_upper_bound_distances;
300 for (const IntegerVariable var : preprocessed_constraint.vars) {
301 const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
302 variable_upper_bound_distances.push_back(ToDouble(var_ub) - lp_values[var]);
303 }
304 // Compute the min cover size.
305 const int smallest_cover_size =
306 GetSmallestCoverSize(preprocessed_constraint, integer_trail);
307
308 std::nth_element(
309 variable_upper_bound_distances.begin(),
310 variable_upper_bound_distances.begin() + smallest_cover_size - 1,
311 variable_upper_bound_distances.end());
312 double cut_lower_bound = 0.0;
313 for (int i = 0; i < smallest_cover_size; ++i) {
314 cut_lower_bound += variable_upper_bound_distances[i];
315 }
316 if (cut_lower_bound >= 1.0 - kMinCutViolation) {
317 VLOG(2) << "Filtered by kappa heuristic";
318 return true;
319 }
320 return false;
321}
322
323double GetKnapsackUpperBound(std::vector<KnapsackItem> items,
324 const double capacity) {
325 // Sort items by value by weight ratio.
326 std::sort(items.begin(), items.end(), std::greater<KnapsackItem>());
327 double left_capacity = capacity;
328 double profit = 0.0;
329 for (const KnapsackItem item : items) {
330 if (item.weight <= left_capacity) {
331 profit += item.profit;
332 left_capacity -= item.weight;
333 } else {
334 profit += (left_capacity / item.weight) * item.profit;
335 break;
336 }
337 }
338 return profit;
339}
340
342 const LinearConstraint& constraint,
344 const IntegerTrail& integer_trail) {
345 std::vector<KnapsackItem> items;
346 double capacity = -ToDouble(constraint.ub) - 1.0;
347 double sum_variable_profit = 0;
348 for (int i = 0; i < constraint.vars.size(); ++i) {
349 const IntegerVariable var = constraint.vars[i];
350 const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
351 const IntegerValue var_lb = integer_trail.LevelZeroLowerBound(var);
352 const IntegerValue coeff = constraint.coeffs[i];
353 KnapsackItem item;
354 item.profit = ToDouble(var_ub) - lp_values[var];
355 item.weight = ToDouble(coeff * (var_ub - var_lb));
356 items.push_back(item);
357 capacity += ToDouble(coeff * var_ub);
358 sum_variable_profit += item.profit;
359 }
360
361 // Return early if the required upper bound is negative since all the profits
362 // are non negative.
363 if (sum_variable_profit - 1.0 + kMinCutViolation < 0.0) return false;
364
365 // Get the knapsack upper bound.
366 const double knapsack_upper_bound =
367 GetKnapsackUpperBound(std::move(items), capacity);
368 if (knapsack_upper_bound < sum_variable_profit - 1.0 + kMinCutViolation) {
369 VLOG(2) << "Filtered by knapsack upper bound";
370 return true;
371 }
372 return false;
373}
374
376 const LinearConstraint& preprocessed_constraint,
378 const IntegerTrail& integer_trail) {
379 if (ConstraintIsTriviallyTrue(preprocessed_constraint, integer_trail)) {
380 return false;
381 }
382 if (CanBeFilteredUsingCutLowerBound(preprocessed_constraint, lp_values,
383 integer_trail)) {
384 return false;
385 }
386 if (CanBeFilteredUsingKnapsackUpperBound(preprocessed_constraint, lp_values,
387 integer_trail)) {
388 return false;
389 }
390 return true;
391}
392
394 std::vector<LinearConstraint>* knapsack_constraints,
395 IntegerTrail* integer_trail) {
396 // If all coefficient are the same, the generated knapsack cuts cannot be
397 // stronger than the constraint itself. However, when we substitute variables
398 // using the implication graph, this is not longer true. So we only skip
399 // constraints with same coeff and no substitutions.
400 if (SmallRangeAndAllCoefficientsMagnitudeAreTheSame(constraint,
401 integer_trail)) {
402 return;
403 }
404 if (constraint.ub < kMaxIntegerValue) {
405 LinearConstraint canonical_knapsack_form;
406
407 // Negate the variables with negative coefficients.
408 for (int i = 0; i < constraint.vars.size(); ++i) {
409 const IntegerVariable var = constraint.vars[i];
410 const IntegerValue coeff = constraint.coeffs[i];
411 if (coeff > IntegerValue(0)) {
412 canonical_knapsack_form.AddTerm(var, coeff);
413 } else {
414 canonical_knapsack_form.AddTerm(NegationOf(var), -coeff);
415 }
416 }
417 canonical_knapsack_form.ub = constraint.ub;
418 canonical_knapsack_form.lb = kMinIntegerValue;
419 knapsack_constraints->push_back(canonical_knapsack_form);
420 }
421
422 if (constraint.lb > kMinIntegerValue) {
423 LinearConstraint canonical_knapsack_form;
424
425 // Negate the variables with positive coefficients.
426 for (int i = 0; i < constraint.vars.size(); ++i) {
427 const IntegerVariable var = constraint.vars[i];
428 const IntegerValue coeff = constraint.coeffs[i];
429 if (coeff > IntegerValue(0)) {
430 canonical_knapsack_form.AddTerm(NegationOf(var), coeff);
431 } else {
432 canonical_knapsack_form.AddTerm(var, -coeff);
433 }
434 }
435 canonical_knapsack_form.ub = -constraint.lb;
436 canonical_knapsack_form.lb = kMinIntegerValue;
437 knapsack_constraints->push_back(canonical_knapsack_form);
438 }
439}
440
441// TODO(user): This is no longer used as we try to separate all cut with
442// knapsack now, remove.
444 const std::vector<LinearConstraint>& base_constraints,
445 const std::vector<IntegerVariable>& vars, Model* model) {
446 CutGenerator result;
447 result.vars = vars;
448
449 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
450 std::vector<LinearConstraint> knapsack_constraints;
451 for (const LinearConstraint& constraint : base_constraints) {
452 // There is often a lot of small linear base constraints and it doesn't seem
453 // super useful to generate cuts for constraints of size 2. Any valid cut
454 // of size 1 should be already infered by the propagation.
455 //
456 // TODO(user): The case of size 2 is a bit less clear. investigate more if
457 // it is useful.
458 if (constraint.vars.size() <= 2) continue;
459
460 ConvertToKnapsackForm(constraint, &knapsack_constraints, integer_trail);
461 }
462 VLOG(1) << "#knapsack constraints: " << knapsack_constraints.size();
463
464 // Note(user): for Knapsack cuts, it seems always advantageous to replace a
465 // variable X by a TIGHT lower bound of the form "coeff * binary + lb". This
466 // will not change "covers" but can only result in more violation by the
467 // current LP solution.
468 ImpliedBoundsProcessor implied_bounds_processor(
469 vars, integer_trail, model->GetOrCreate<ImpliedBounds>());
470
471 // TODO(user): do not add generator if there are no knapsack constraints.
472 result.generate_cuts = [implied_bounds_processor, knapsack_constraints, vars,
473 model, integer_trail](
475 lp_values,
476 LinearConstraintManager* manager) mutable {
477 // TODO(user): When we use implied-bound substitution, we might still infer
478 // an interesting cut even if all variables are integer. See if we still
479 // want to skip all such constraints.
480 if (AllVarsTakeIntegerValue(vars, lp_values)) return true;
481
482 KnapsackSolverForCuts knapsack_solver(
483 "Knapsack on demand cover cut generator");
484 int64_t skipped_constraints = 0;
485 LinearConstraint mutable_constraint;
486
487 // Iterate through all knapsack constraints.
488 implied_bounds_processor.RecomputeCacheAndSeparateSomeImpliedBoundCuts(
489 lp_values);
490 for (const LinearConstraint& constraint : knapsack_constraints) {
491 if (model->GetOrCreate<TimeLimit>()->LimitReached()) break;
492 VLOG(2) << "Processing constraint: " << constraint.DebugString();
493
494 mutable_constraint = constraint;
495 implied_bounds_processor.ProcessUpperBoundedConstraint(
496 lp_values, &mutable_constraint);
497 MakeAllCoefficientsPositive(&mutable_constraint);
498
499 const LinearConstraint preprocessed_constraint =
500 GetPreprocessedLinearConstraint(mutable_constraint, lp_values,
501 *integer_trail);
502 if (preprocessed_constraint.vars.empty()) continue;
503
504 if (!CanFormValidKnapsackCover(preprocessed_constraint, lp_values,
505 *integer_trail)) {
506 skipped_constraints++;
507 continue;
508 }
509
510 // Profits are (upper_bounds[i] - lp_values[i]) for knapsack variables.
511 std::vector<double> profits;
512 profits.reserve(preprocessed_constraint.vars.size());
513
514 // Weights are (coeffs[i] * (upper_bound[i] - lower_bound[i])).
515 std::vector<double> weights;
516 weights.reserve(preprocessed_constraint.vars.size());
517
518 double capacity = -ToDouble(preprocessed_constraint.ub) - 1.0;
519
520 // Compute and store the sum of variable profits. This is the constant
521 // part of the objective of the problem we are trying to solve. Hence
522 // this part is not supplied to the knapsack_solver and is subtracted
523 // when we receive the knapsack solution.
524 double sum_variable_profit = 0;
525
526 // Compute the profits, the weights and the capacity for the knapsack
527 // instance.
528 for (int i = 0; i < preprocessed_constraint.vars.size(); ++i) {
529 const IntegerVariable var = preprocessed_constraint.vars[i];
530 const double coefficient = ToDouble(preprocessed_constraint.coeffs[i]);
531 const double var_ub = ToDouble(integer_trail->LevelZeroUpperBound(var));
532 const double var_lb = ToDouble(integer_trail->LevelZeroLowerBound(var));
533 const double variable_profit = var_ub - lp_values[var];
534 profits.push_back(variable_profit);
535
536 sum_variable_profit += variable_profit;
537
538 const double weight = coefficient * (var_ub - var_lb);
539 weights.push_back(weight);
540 capacity += weight + coefficient * var_lb;
541 }
542 if (capacity < 0.0) continue;
543
544 std::vector<IntegerVariable> cut_vars;
545 std::vector<IntegerValue> cut_vars_original_coefficients;
546
547 VLOG(2) << "Knapsack size: " << profits.size();
548 knapsack_solver.Init(profits, weights, capacity);
549
550 // Set the time limit for the knapsack solver.
551 const double time_limit_for_knapsack_solver =
552 model->GetOrCreate<TimeLimit>()->GetTimeLeft();
553
554 // Solve the instance and subtract the constant part to compute the
555 // sum_of_distance_to_ub_for_vars_in_cover.
556 // TODO(user): Consider solving the instance approximately.
557 bool is_solution_optimal = false;
559 sum_variable_profit - 1.0 + kMinCutViolation);
560 // TODO(user): Consider providing lower bound threshold as
561 // sum_variable_profit - 1.0 + kMinCutViolation.
562 // TODO(user): Set node limit for knapsack solver.
563 auto time_limit_for_solver =
564 absl::make_unique<TimeLimit>(time_limit_for_knapsack_solver);
565 const double sum_of_distance_to_ub_for_vars_in_cover =
566 sum_variable_profit -
567 knapsack_solver.Solve(time_limit_for_solver.get(),
568 &is_solution_optimal);
569 if (is_solution_optimal) {
570 VLOG(2) << "Knapsack Optimal solution found yay !";
571 }
572 if (time_limit_for_solver->LimitReached()) {
573 VLOG(1) << "Knapsack Solver run out of time limit.";
574 }
575 if (sum_of_distance_to_ub_for_vars_in_cover < 1.0 - kMinCutViolation) {
576 // Constraint is eligible for the cover.
577
578 IntegerValue constraint_ub_for_cut = preprocessed_constraint.ub;
579 std::set<IntegerVariable> vars_in_cut;
580 for (int i = 0; i < preprocessed_constraint.vars.size(); ++i) {
581 const IntegerVariable var = preprocessed_constraint.vars[i];
582 const IntegerValue coefficient = preprocessed_constraint.coeffs[i];
583 if (!knapsack_solver.best_solution(i)) {
584 cut_vars.push_back(var);
585 cut_vars_original_coefficients.push_back(coefficient);
586 vars_in_cut.insert(var);
587 } else {
588 const IntegerValue var_lb = integer_trail->LevelZeroLowerBound(var);
589 constraint_ub_for_cut -= coefficient * var_lb;
590 }
591 }
592 LinearConstraint cut = GenerateKnapsackCutForCover(
593 cut_vars, cut_vars_original_coefficients, constraint_ub_for_cut,
594 *integer_trail);
595
596 // Check if the constraint has only binary variables.
597 bool is_lifted = false;
598 if (ConstraintIsEligibleForLifting(cut, *integer_trail)) {
599 if (LiftKnapsackCut(mutable_constraint, lp_values,
600 cut_vars_original_coefficients, *integer_trail,
601 model->GetOrCreate<TimeLimit>(), &cut)) {
602 is_lifted = true;
603 }
604 }
605
606 CHECK(!SolutionSatisfiesConstraint(cut, lp_values));
607 manager->AddCut(cut, is_lifted ? "LiftedKnapsack" : "Knapsack",
608 lp_values);
609 }
610 }
611 if (skipped_constraints > 0) {
612 VLOG(2) << "Skipped constraints: " << skipped_constraints;
613 }
614 return true;
615 };
616
617 return result;
618}
619
620// Compute the larger t <= max_t such that t * rhs_remainder >= divisor / 2.
621//
622// This is just a separate function as it is slightly faster to compute the
623// result only once.
624IntegerValue GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor,
625 IntegerValue max_t) {
626 DCHECK_GE(max_t, 1);
627 return rhs_remainder == 0
628 ? max_t
629 : std::min(max_t, CeilRatio(divisor / 2, rhs_remainder));
630}
631
632std::function<IntegerValue(IntegerValue)> GetSuperAdditiveRoundingFunction(
633 IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t,
634 IntegerValue max_scaling) {
635 DCHECK_GE(max_scaling, 1);
636 DCHECK_GE(t, 1);
637
638 // Adjust after the multiplication by t.
639 rhs_remainder *= t;
640 DCHECK_LT(rhs_remainder, divisor);
641
642 // Make sure we don't have an integer overflow below. Note that we assume that
643 // divisor and the maximum coeff magnitude are not too different (maybe a
644 // factor 1000 at most) so that the final result will never overflow.
645 max_scaling =
646 std::min(max_scaling, std::numeric_limits<int64_t>::max() / divisor);
647
648 const IntegerValue size = divisor - rhs_remainder;
649 if (max_scaling == 1 || size == 1) {
650 // TODO(user): Use everywhere a two step computation to avoid overflow?
651 // First divide by divisor, then multiply by t. For now, we limit t so that
652 // we never have an overflow instead.
653 return [t, divisor](IntegerValue coeff) {
654 return FloorRatio(t * coeff, divisor);
655 };
656 } else if (size <= max_scaling) {
657 return [size, rhs_remainder, t, divisor](IntegerValue coeff) {
658 const IntegerValue t_coeff = t * coeff;
659 const IntegerValue ratio = FloorRatio(t_coeff, divisor);
660 const IntegerValue remainder = PositiveRemainder(t_coeff, divisor);
661 const IntegerValue diff = remainder - rhs_remainder;
662 return size * ratio + std::max(IntegerValue(0), diff);
663 };
664 } else if (max_scaling.value() * rhs_remainder.value() < divisor) {
665 // Because of our max_t limitation, the rhs_remainder might stay small.
666 //
667 // If it is "too small" we cannot use the code below because it will not be
668 // valid. So we just divide divisor into max_scaling bucket. The
669 // rhs_remainder will be in the bucket 0.
670 //
671 // Note(user): This seems the same as just increasing t, modulo integer
672 // overflows. Maybe we should just always do the computation like this so
673 // that we can use larger t even if coeff is close to kint64max.
674 return [t, divisor, max_scaling](IntegerValue coeff) {
675 const IntegerValue t_coeff = t * coeff;
676 const IntegerValue ratio = FloorRatio(t_coeff, divisor);
677 const IntegerValue remainder = PositiveRemainder(t_coeff, divisor);
678 const IntegerValue bucket = FloorRatio(remainder * max_scaling, divisor);
679 return max_scaling * ratio + bucket;
680 };
681 } else {
682 // We divide (size = divisor - rhs_remainder) into (max_scaling - 1) buckets
683 // and increase the function by 1 / max_scaling for each of them.
684 //
685 // Note that for different values of max_scaling, we get a family of
686 // functions that do not dominate each others. So potentially, a max scaling
687 // as low as 2 could lead to the better cut (this is exactly the Letchford &
688 // Lodi function).
689 //
690 // Another interesting fact, is that if we want to compute the maximum alpha
691 // for a constraint with 2 terms like:
692 // divisor * Y + (ratio * divisor + remainder) * X
693 // <= rhs_ratio * divisor + rhs_remainder
694 // so that we have the cut:
695 // Y + (ratio + alpha) * X <= rhs_ratio
696 // This is the same as computing the maximum alpha such that for all integer
697 // X > 0 we have CeilRatio(alpha * divisor * X, divisor)
698 // <= CeilRatio(remainder * X - rhs_remainder, divisor).
699 // We can prove that this alpha is of the form (n - 1) / n, and it will
700 // be reached by such function for a max_scaling of n.
701 //
702 // TODO(user): This function is not always maximal when
703 // size % (max_scaling - 1) == 0. Improve?
704 return [size, rhs_remainder, t, divisor, max_scaling](IntegerValue coeff) {
705 const IntegerValue t_coeff = t * coeff;
706 const IntegerValue ratio = FloorRatio(t_coeff, divisor);
707 const IntegerValue remainder = PositiveRemainder(t_coeff, divisor);
708 const IntegerValue diff = remainder - rhs_remainder;
709 const IntegerValue bucket =
710 diff > 0 ? CeilRatio(diff * (max_scaling - 1), size)
711 : IntegerValue(0);
712 return max_scaling * ratio + bucket;
713 };
714 }
715}
716
717// TODO(user): This has been optimized a bit, but we can probably do even better
718// as it still takes around 25% percent of the run time when all the cuts are on
719// for the opm*mps.gz problems and others.
721 RoundingOptions options, const std::vector<double>& lp_values,
722 const std::vector<IntegerValue>& lower_bounds,
723 const std::vector<IntegerValue>& upper_bounds,
724 ImpliedBoundsProcessor* ib_processor, LinearConstraint* cut) {
725 const int size = lp_values.size();
726 if (size == 0) return;
727 CHECK_EQ(lower_bounds.size(), size);
728 CHECK_EQ(upper_bounds.size(), size);
729 CHECK_EQ(cut->vars.size(), size);
730 CHECK_EQ(cut->coeffs.size(), size);
732
733 // To optimize the computation of the best divisor below, we only need to
734 // look at the indices with a shifted lp value that is not close to zero.
735 //
736 // TODO(user): sort by decreasing lp_values so that our early abort test in
737 // the critical loop below has more chance of returning early? I tried but it
738 // didn't seems to change much though.
739 relevant_indices_.clear();
740 relevant_lp_values_.clear();
741 relevant_coeffs_.clear();
742 relevant_bound_diffs_.clear();
743 divisors_.clear();
744 adjusted_coeffs_.clear();
745
746 // Compute the maximum magnitude for non-fixed variables.
747 IntegerValue max_magnitude(0);
748 for (int i = 0; i < size; ++i) {
749 if (lower_bounds[i] == upper_bounds[i]) continue;
750 const IntegerValue magnitude = IntTypeAbs(cut->coeffs[i]);
751 max_magnitude = std::max(max_magnitude, magnitude);
752 }
753
754 // Shift each variable using its lower/upper bound so that no variable can
755 // change sign. We eventually do a change of variable to its negation so
756 // that all variable are non-negative.
757 bool overflow = false;
758 change_sign_at_postprocessing_.assign(size, false);
759 for (int i = 0; i < size; ++i) {
760 if (cut->coeffs[i] == 0) continue;
761 const IntegerValue magnitude = IntTypeAbs(cut->coeffs[i]);
762
763 // We might change them below.
764 IntegerValue lb = lower_bounds[i];
765 double lp_value = lp_values[i];
766
767 const IntegerValue ub = upper_bounds[i];
768 const IntegerValue bound_diff =
769 IntegerValue(CapSub(ub.value(), lb.value()));
770 // Note that since we use ToDouble() this code works fine with lb/ub at
771 // min/max integer value.
772 //
773 // TODO(user): Experiments with different heuristics. Other solver also
774 // seems to try a bunch of possibilities in a "postprocess" phase once
775 // the divisor is chosen. Try that.
776 {
777 // when the magnitude of the entry become smaller and smaller we bias
778 // towards a positive coefficient. This is because after rounding this
779 // will likely become zero instead of -divisor and we need the lp value
780 // to be really close to its bound to compensate.
781 const double lb_dist = std::abs(lp_value - ToDouble(lb));
782 const double ub_dist = std::abs(lp_value - ToDouble(ub));
783 const double bias =
784 std::max(1.0, 0.1 * ToDouble(max_magnitude) / ToDouble(magnitude));
785 if ((bias * lb_dist > ub_dist && cut->coeffs[i] < 0) ||
786 (lb_dist > bias * ub_dist && cut->coeffs[i] > 0)) {
787 change_sign_at_postprocessing_[i] = true;
788 cut->coeffs[i] = -cut->coeffs[i];
789 lp_value = -lp_value;
790 lb = -ub;
791 }
792 }
793
794 // Always shift to lb.
795 // coeff * X = coeff * (X - shift) + coeff * shift.
796 lp_value -= ToDouble(lb);
797 if (!AddProductTo(-cut->coeffs[i], lb, &cut->ub)) {
798 overflow = true;
799 break;
800 }
801
802 // Deal with fixed variable, no need to shift back in this case, we can
803 // just remove the term.
804 if (bound_diff == 0) {
805 cut->coeffs[i] = IntegerValue(0);
806 lp_value = 0.0;
807 }
808
809 if (std::abs(lp_value) > 1e-2) {
810 relevant_coeffs_.push_back(cut->coeffs[i]);
811 relevant_indices_.push_back(i);
812 relevant_lp_values_.push_back(lp_value);
813 relevant_bound_diffs_.push_back(bound_diff);
814 divisors_.push_back(magnitude);
815 }
816 }
817
818 // TODO(user): Maybe this shouldn't be called on such constraint.
819 if (relevant_coeffs_.empty()) {
820 VLOG(2) << "Issue, nothing to cut.";
821 *cut = LinearConstraint(IntegerValue(0), IntegerValue(0));
822 return;
823 }
824 CHECK_NE(max_magnitude, 0);
825
826 // Our heuristic will try to generate a few different cuts, and we will keep
827 // the most violated one scaled by the l2 norm of the relevant position.
828 //
829 // TODO(user): Experiment for the best value of this initial violation
830 // threshold. Note also that we use the l2 norm on the restricted position
831 // here. Maybe we should change that? On that note, the L2 norm usage seems a
832 // bit weird to me since it grows with the number of term in the cut. And
833 // often, we already have a good cut, and we make it stronger by adding extra
834 // terms that do not change its activity.
835 //
836 // The discussion above only concern the best_scaled_violation initial value.
837 // The remainder_threshold allows to not consider cuts for which the final
838 // efficacity is clearly lower than 1e-3 (it is a bound, so we could generate
839 // cuts with a lower efficacity than this).
840 double best_scaled_violation = 0.01;
841 const IntegerValue remainder_threshold(max_magnitude / 1000);
842
843 // The cut->ub might have grown quite a bit with the bound substitution, so
844 // we need to include it too since we will apply the rounding function on it.
845 max_magnitude = std::max(max_magnitude, IntTypeAbs(cut->ub));
846
847 // Make sure that when we multiply the rhs or the coefficient by a factor t,
848 // we do not have an integer overflow. Actually, we need a bit more room
849 // because we might round down a value to the next multiple of
850 // max_magnitude.
851 const IntegerValue threshold = kMaxIntegerValue / 2;
852 if (overflow || max_magnitude >= threshold) {
853 VLOG(2) << "Issue, overflow.";
854 *cut = LinearConstraint(IntegerValue(0), IntegerValue(0));
855 return;
856 }
857 const IntegerValue max_t = threshold / max_magnitude;
858
859 // There is no point trying twice the same divisor or a divisor that is too
860 // small. Note that we use a higher threshold than the remainder_threshold
861 // because we can boost the remainder thanks to our adjusting heuristic below
862 // and also because this allows to have cuts with a small range of
863 // coefficients.
864 //
865 // TODO(user): Note that the std::sort() is visible in some cpu profile.
866 {
867 int new_size = 0;
868 const IntegerValue divisor_threshold = max_magnitude / 10;
869 for (int i = 0; i < divisors_.size(); ++i) {
870 if (divisors_[i] <= divisor_threshold) continue;
871 divisors_[new_size++] = divisors_[i];
872 }
873 divisors_.resize(new_size);
874 }
875 gtl::STLSortAndRemoveDuplicates(&divisors_, std::greater<IntegerValue>());
876
877 // TODO(user): Avoid quadratic algorithm? Note that we are quadratic in
878 // relevant_indices not the full cut->coeffs.size(), but this is still too
879 // much on some problems.
880 IntegerValue best_divisor(0);
881 for (const IntegerValue divisor : divisors_) {
882 // Skip if we don't have the potential to generate a good enough cut.
883 const IntegerValue initial_rhs_remainder =
884 cut->ub - FloorRatio(cut->ub, divisor) * divisor;
885 if (initial_rhs_remainder <= remainder_threshold) continue;
886
887 IntegerValue temp_ub = cut->ub;
888 adjusted_coeffs_.clear();
889
890 // We will adjust coefficient that are just under an exact multiple of
891 // divisor to an exact multiple. This is meant to get rid of small errors
892 // that appears due to rounding error in our exact computation of the
893 // initial constraint given to this class.
894 //
895 // Each adjustement will cause the initial_rhs_remainder to increase, and we
896 // do not want to increase it above divisor. Our threshold below guarantees
897 // this. Note that the higher the rhs_remainder becomes, the more the
898 // function f() has a chance to reduce the violation, so it is not always a
899 // good idea to use all the slack we have between initial_rhs_remainder and
900 // divisor.
901 //
902 // TODO(user): If possible, it might be better to complement these
903 // variables. Even if the adjusted lp_values end up larger, if we loose less
904 // when taking f(), then we will have a better violation.
905 const IntegerValue adjust_threshold =
906 (divisor - initial_rhs_remainder - 1) / IntegerValue(size);
907 if (adjust_threshold > 0) {
908 // Even before we finish the adjust, we can have a lower bound on the
909 // activily loss using this divisor, and so we can abort early. This is
910 // similar to what is done below in the function.
911 bool early_abort = false;
912 double loss_lb = 0.0;
913 const double threshold = ToDouble(initial_rhs_remainder);
914
915 for (int i = 0; i < relevant_coeffs_.size(); ++i) {
916 // Compute the difference of coeff with the next multiple of divisor.
917 const IntegerValue coeff = relevant_coeffs_[i];
918 const IntegerValue remainder =
919 CeilRatio(coeff, divisor) * divisor - coeff;
920
921 if (divisor - remainder <= initial_rhs_remainder) {
922 // We do not know exactly f() yet, but it will always round to the
923 // floor of the division by divisor in this case.
924 loss_lb += ToDouble(divisor - remainder) * relevant_lp_values_[i];
925 if (loss_lb >= threshold) {
926 early_abort = true;
927 break;
928 }
929 }
930
931 // Adjust coeff of the form k * divisor - epsilon.
932 const IntegerValue diff = relevant_bound_diffs_[i];
933 if (remainder > 0 && remainder <= adjust_threshold &&
934 CapProd(diff.value(), remainder.value()) <= adjust_threshold) {
935 temp_ub += remainder * diff;
936 adjusted_coeffs_.push_back({i, coeff + remainder});
937 }
938 }
939
940 if (early_abort) continue;
941 }
942
943 // Create the super-additive function f().
944 const IntegerValue rhs_remainder =
945 temp_ub - FloorRatio(temp_ub, divisor) * divisor;
946 if (rhs_remainder == 0) continue;
947
949 rhs_remainder, divisor, GetFactorT(rhs_remainder, divisor, max_t),
950 options.max_scaling);
951
952 // As we round coefficients, we will compute the loss compared to the
953 // current scaled constraint activity. As soon as this loss crosses the
954 // slack, then we known that there is no violation and we can abort early.
955 //
956 // TODO(user): modulo the scaling, we could compute the exact threshold
957 // using our current best cut. Note that we also have to account the change
958 // in slack due to the adjust code above.
959 const double scaling = ToDouble(f(divisor)) / ToDouble(divisor);
960 const double threshold = scaling * ToDouble(rhs_remainder);
961 double loss = 0.0;
962
963 // Apply f() to the cut and compute the cut violation. Note that it is
964 // okay to just look at the relevant indices since the other have a lp
965 // value which is almost zero. Doing it like this is faster, and even if
966 // the max_magnitude might be off it should still be relevant enough.
967 double violation = -ToDouble(f(temp_ub));
968 double l2_norm = 0.0;
969 bool early_abort = false;
970 int adjusted_coeffs_index = 0;
971 for (int i = 0; i < relevant_coeffs_.size(); ++i) {
972 IntegerValue coeff = relevant_coeffs_[i];
973
974 // Adjust coeff according to our previous computation if needed.
975 if (adjusted_coeffs_index < adjusted_coeffs_.size() &&
976 adjusted_coeffs_[adjusted_coeffs_index].first == i) {
977 coeff = adjusted_coeffs_[adjusted_coeffs_index].second;
978 adjusted_coeffs_index++;
979 }
980
981 if (coeff == 0) continue;
982 const IntegerValue new_coeff = f(coeff);
983 const double new_coeff_double = ToDouble(new_coeff);
984 const double lp_value = relevant_lp_values_[i];
985
986 l2_norm += new_coeff_double * new_coeff_double;
987 violation += new_coeff_double * lp_value;
988 loss += (scaling * ToDouble(coeff) - new_coeff_double) * lp_value;
989 if (loss >= threshold) {
990 early_abort = true;
991 break;
992 }
993 }
994 if (early_abort) continue;
995
996 // Here we scale by the L2 norm over the "relevant" positions. This seems
997 // to work slighly better in practice.
998 violation /= sqrt(l2_norm);
999 if (violation > best_scaled_violation) {
1000 best_scaled_violation = violation;
1001 best_divisor = divisor;
1002 }
1003 }
1004
1005 if (best_divisor == 0) {
1006 *cut = LinearConstraint(IntegerValue(0), IntegerValue(0));
1007 return;
1008 }
1009
1010 // Adjust coefficients.
1011 //
1012 // TODO(user): It might make sense to also adjust the one with a small LP
1013 // value, but then the cut will be slighlty different than the one we computed
1014 // above. Try with and without maybe?
1015 const IntegerValue initial_rhs_remainder =
1016 cut->ub - FloorRatio(cut->ub, best_divisor) * best_divisor;
1017 const IntegerValue adjust_threshold =
1018 (best_divisor - initial_rhs_remainder - 1) / IntegerValue(size);
1019 if (adjust_threshold > 0) {
1020 for (int i = 0; i < relevant_indices_.size(); ++i) {
1021 const int index = relevant_indices_[i];
1022 const IntegerValue diff = relevant_bound_diffs_[i];
1023 if (diff > adjust_threshold) continue;
1024
1025 // Adjust coeff of the form k * best_divisor - epsilon.
1026 const IntegerValue coeff = cut->coeffs[index];
1027 const IntegerValue remainder =
1028 CeilRatio(coeff, best_divisor) * best_divisor - coeff;
1029 if (CapProd(diff.value(), remainder.value()) <= adjust_threshold) {
1030 cut->ub += remainder * diff;
1031 cut->coeffs[index] += remainder;
1032 }
1033 }
1034 }
1035
1036 // Create the super-additive function f().
1037 //
1038 // TODO(user): Try out different rounding function and keep the best. We can
1039 // change max_t and max_scaling. It might not be easy to choose which cut is
1040 // the best, but we can at least know for sure if one dominate the other
1041 // completely. That is, if for all coeff f(coeff)/f(divisor) is greater than
1042 // or equal to the same value for another function f.
1043 const IntegerValue rhs_remainder =
1044 cut->ub - FloorRatio(cut->ub, best_divisor) * best_divisor;
1045 IntegerValue factor_t = GetFactorT(rhs_remainder, best_divisor, max_t);
1046 auto f = GetSuperAdditiveRoundingFunction(rhs_remainder, best_divisor,
1047 factor_t, options.max_scaling);
1048
1049 // Look amongst all our possible function f() for one that dominate greedily
1050 // our current best one. Note that we prefer lower scaling factor since that
1051 // result in a cut with lower coefficients.
1052 remainders_.clear();
1053 for (int i = 0; i < size; ++i) {
1054 const IntegerValue coeff = cut->coeffs[i];
1055 const IntegerValue r =
1056 coeff - FloorRatio(coeff, best_divisor) * best_divisor;
1057 if (r > rhs_remainder) remainders_.push_back(r);
1058 }
1059 gtl::STLSortAndRemoveDuplicates(&remainders_);
1060 if (remainders_.size() <= 100) {
1061 best_rs_.clear();
1062 for (const IntegerValue r : remainders_) {
1063 best_rs_.push_back(f(r));
1064 }
1065 IntegerValue best_d = f(best_divisor);
1066
1067 // Note that the complexity seems high 100 * 2 * options.max_scaling, but
1068 // this only run on cuts that are already efficient and the inner loop tend
1069 // to abort quickly. I didn't see this code in the cpu profile so far.
1070 for (const IntegerValue t :
1071 {IntegerValue(1), GetFactorT(rhs_remainder, best_divisor, max_t)}) {
1072 for (IntegerValue s(2); s <= options.max_scaling; ++s) {
1073 const auto g =
1074 GetSuperAdditiveRoundingFunction(rhs_remainder, best_divisor, t, s);
1075 int num_strictly_better = 0;
1076 rs_.clear();
1077 const IntegerValue d = g(best_divisor);
1078 for (int i = 0; i < best_rs_.size(); ++i) {
1079 const IntegerValue temp = g(remainders_[i]);
1080 if (temp * best_d < best_rs_[i] * d) break;
1081 if (temp * best_d > best_rs_[i] * d) num_strictly_better++;
1082 rs_.push_back(temp);
1083 }
1084 if (rs_.size() == best_rs_.size() && num_strictly_better > 0) {
1085 f = g;
1086 factor_t = t;
1087 best_rs_ = rs_;
1088 best_d = d;
1089 }
1090 }
1091 }
1092 }
1093
1094 // Starts to apply f() to the cut. We only apply it to the rhs here, the
1095 // coefficient will be done after the potential lifting of some Booleans.
1096 cut->ub = f(cut->ub);
1097 tmp_terms_.clear();
1098
1099 // Lift some implied bounds Booleans. Note that we will add them after
1100 // "size" so they will be ignored in the second loop below.
1101 num_lifted_booleans_ = 0;
1102 if (ib_processor != nullptr) {
1103 for (int i = 0; i < size; ++i) {
1104 const IntegerValue coeff = cut->coeffs[i];
1105 if (coeff == 0) continue;
1106
1107 IntegerVariable var = cut->vars[i];
1108 if (change_sign_at_postprocessing_[i]) {
1109 var = NegationOf(var);
1110 }
1111
1113 ib_processor->GetCachedImpliedBoundInfo(var);
1114
1115 // Avoid overflow.
1116 if (CapProd(CapProd(std::abs(coeff.value()), factor_t.value()),
1117 info.bound_diff.value()) ==
1119 continue;
1120 }
1121
1122 // Because X = bound_diff * B + S
1123 // We can replace coeff * X by the expression before applying f:
1124 // = f(coeff * bound_diff) * B + f(coeff) * [X - bound_diff * B]
1125 // = f(coeff) * X + (f(coeff * bound_diff) - f(coeff) * bound_diff] B
1126 // So we can "lift" B into the cut.
1127 const IntegerValue coeff_b =
1128 f(coeff * info.bound_diff) - f(coeff) * info.bound_diff;
1129 CHECK_GE(coeff_b, 0);
1130 if (coeff_b == 0) continue;
1131
1132 ++num_lifted_booleans_;
1133 if (info.is_positive) {
1134 tmp_terms_.push_back({info.bool_var, coeff_b});
1135 } else {
1136 tmp_terms_.push_back({info.bool_var, -coeff_b});
1137 cut->ub = CapAdd(-coeff_b.value(), cut->ub.value());
1138 }
1139 }
1140 }
1141
1142 // Apply f() to the cut.
1143 //
1144 // Remove the bound shifts so the constraint is expressed in the original
1145 // variables.
1146 for (int i = 0; i < size; ++i) {
1147 IntegerValue coeff = cut->coeffs[i];
1148 if (coeff == 0) continue;
1149 coeff = f(coeff);
1150 if (coeff == 0) continue;
1151 if (change_sign_at_postprocessing_[i]) {
1152 cut->ub = IntegerValue(
1153 CapAdd((coeff * -upper_bounds[i]).value(), cut->ub.value()));
1154 tmp_terms_.push_back({cut->vars[i], -coeff});
1155 } else {
1156 cut->ub = IntegerValue(
1157 CapAdd((coeff * lower_bounds[i]).value(), cut->ub.value()));
1158 tmp_terms_.push_back({cut->vars[i], coeff});
1159 }
1160 }
1161
1162 // Basic post-processing.
1163 CleanTermsAndFillConstraint(&tmp_terms_, cut);
1164 RemoveZeroTerms(cut);
1165 DivideByGCD(cut);
1166}
1167
1169 const LinearConstraint base_ct, const std::vector<double>& lp_values,
1170 const std::vector<IntegerValue>& lower_bounds,
1171 const std::vector<IntegerValue>& upper_bounds) {
1172 const int base_size = lp_values.size();
1173
1174 // Fill terms with a rewrite of the base constraint where all coeffs &
1175 // variables are positive by using either (X - LB) or (UB - X) as new
1176 // variables.
1177 terms_.clear();
1178 IntegerValue rhs = base_ct.ub;
1179 IntegerValue sum_of_diff(0);
1180 IntegerValue max_base_magnitude(0);
1181 for (int i = 0; i < base_size; ++i) {
1182 const IntegerValue coeff = base_ct.coeffs[i];
1183 const IntegerValue positive_coeff = IntTypeAbs(coeff);
1184 max_base_magnitude = std::max(max_base_magnitude, positive_coeff);
1185 const IntegerValue bound_diff = upper_bounds[i] - lower_bounds[i];
1186 if (!AddProductTo(positive_coeff, bound_diff, &sum_of_diff)) {
1187 return false;
1188 }
1189 const IntegerValue diff = positive_coeff * bound_diff;
1190 if (coeff > 0) {
1191 if (!AddProductTo(-coeff, lower_bounds[i], &rhs)) return false;
1192 terms_.push_back(
1193 {i, ToDouble(upper_bounds[i]) - lp_values[i], positive_coeff, diff});
1194 } else {
1195 if (!AddProductTo(-coeff, upper_bounds[i], &rhs)) return false;
1196 terms_.push_back(
1197 {i, lp_values[i] - ToDouble(lower_bounds[i]), positive_coeff, diff});
1198 }
1199 }
1200
1201 // Try a simple cover heuristic.
1202 // Look for violated CUT of the form: sum (UB - X) or (X - LB) >= 1.
1203 double activity = 0.0;
1204 int new_size = 0;
1205 std::sort(terms_.begin(), terms_.end(), [](const Term& a, const Term& b) {
1206 if (a.dist_to_max_value == b.dist_to_max_value) {
1207 // Prefer low coefficients if the distance is the same.
1208 return a.positive_coeff < b.positive_coeff;
1209 }
1210 return a.dist_to_max_value < b.dist_to_max_value;
1211 });
1212 for (int i = 0; i < terms_.size(); ++i) {
1213 const Term& term = terms_[i];
1214 activity += term.dist_to_max_value;
1215
1216 // As an heuristic we select all the term so that the sum of distance
1217 // to the upper bound is <= 1.0. If the corresponding rhs is negative, then
1218 // we will have a cut of violation at least 0.0. Note that this violation
1219 // can be improved by the lifting.
1220 //
1221 // TODO(user): experiment with different threshold (even greater than one).
1222 // Or come up with an algo that incorporate the lifting into the heuristic.
1223 if (activity > 1.0) {
1224 new_size = i; // before this entry.
1225 break;
1226 }
1227
1228 rhs -= term.diff;
1229 }
1230
1231 // If the rhs is now negative, we have a cut.
1232 //
1233 // Note(user): past this point, now that a given "base" cover has been chosen,
1234 // we basically compute the cut (of the form sum X <= bound) with the maximum
1235 // possible violation. Note also that we lift as much as possible, so we don't
1236 // necessarily optimize for the cut efficacity though. But we do get a
1237 // stronger cut.
1238 if (rhs >= 0) return false;
1239 if (new_size == 0) return false;
1240
1241 // Transform to a minimal cover. We want to greedily remove the largest coeff
1242 // first, so we have more chance for the "lifting" below which can increase
1243 // the cut violation. If the coeff are the same, we prefer to remove high
1244 // distance from upper bound first.
1245 //
1246 // We compute the cut at the same time.
1247 terms_.resize(new_size);
1248 std::sort(terms_.begin(), terms_.end(), [](const Term& a, const Term& b) {
1249 if (a.positive_coeff == b.positive_coeff) {
1250 return a.dist_to_max_value > b.dist_to_max_value;
1251 }
1252 return a.positive_coeff > b.positive_coeff;
1253 });
1254 in_cut_.assign(base_ct.vars.size(), false);
1255 cut_.ClearTerms();
1256 cut_.lb = kMinIntegerValue;
1257 cut_.ub = IntegerValue(-1);
1258 IntegerValue max_coeff(0);
1259 for (const Term term : terms_) {
1260 if (term.diff + rhs < 0) {
1261 rhs += term.diff;
1262 continue;
1263 }
1264 in_cut_[term.index] = true;
1265 max_coeff = std::max(max_coeff, term.positive_coeff);
1266 cut_.vars.push_back(base_ct.vars[term.index]);
1267 if (base_ct.coeffs[term.index] > 0) {
1268 cut_.coeffs.push_back(IntegerValue(1));
1269 cut_.ub += upper_bounds[term.index];
1270 } else {
1271 cut_.coeffs.push_back(IntegerValue(-1));
1272 cut_.ub -= lower_bounds[term.index];
1273 }
1274 }
1275
1276 // In case the max_coeff variable is not binary, it might be possible to
1277 // tighten the cut a bit more.
1278 //
1279 // Note(user): I never observed this on the miplib so far.
1280 if (max_coeff == 0) return true;
1281 if (max_coeff < -rhs) {
1282 const IntegerValue m = FloorRatio(-rhs - 1, max_coeff);
1283 rhs += max_coeff * m;
1284 cut_.ub -= m;
1285 }
1286 CHECK_LT(rhs, 0);
1287
1288 // Lift all at once the variables not used in the cover.
1289 //
1290 // We have a cut of the form sum_i X_i <= b that we will lift into
1291 // sum_i scaling X_i + sum f(base_coeff_j) X_j <= b * scaling.
1292 //
1293 // Using the super additivity of f() and how we construct it,
1294 // we know that: sum_j base_coeff_j X_j <= N * max_coeff + (max_coeff - slack)
1295 // implies that: sum_j f(base_coeff_j) X_j <= N * scaling.
1296 //
1297 // 1/ cut > b -(N+1) => original sum + (N+1) * max_coeff >= rhs + slack
1298 // 2/ rewrite 1/ as : scaling * cut >= scaling * b - scaling * N => ...
1299 // 3/ lift > N * scaling => lift_sum > N * max_coeff + (max_coeff - slack)
1300 // And adding 2/ + 3/ we prove what we want:
1301 // cut * scaling + lift > b * scaling => original_sum + lift_sum > rhs.
1302 const IntegerValue slack = -rhs;
1303 const IntegerValue remainder = max_coeff - slack;
1304 max_base_magnitude = std::max(max_base_magnitude, IntTypeAbs(cut_.ub));
1305 const IntegerValue max_scaling(std::min(
1306 IntegerValue(60), FloorRatio(kMaxIntegerValue, max_base_magnitude)));
1307 const auto f = GetSuperAdditiveRoundingFunction(remainder, max_coeff,
1308 IntegerValue(1), max_scaling);
1309
1310 const IntegerValue scaling = f(max_coeff);
1311 if (scaling > 1) {
1312 for (int i = 0; i < cut_.coeffs.size(); ++i) cut_.coeffs[i] *= scaling;
1313 cut_.ub *= scaling;
1314 }
1315
1316 num_lifting_ = 0;
1317 for (int i = 0; i < base_size; ++i) {
1318 if (in_cut_[i]) continue;
1319 const IntegerValue positive_coeff = IntTypeAbs(base_ct.coeffs[i]);
1320 const IntegerValue new_coeff = f(positive_coeff);
1321 if (new_coeff == 0) continue;
1322
1323 ++num_lifting_;
1324 if (base_ct.coeffs[i] > 0) {
1325 // Add new_coeff * (X - LB)
1326 cut_.coeffs.push_back(new_coeff);
1327 cut_.vars.push_back(base_ct.vars[i]);
1328 cut_.ub += lower_bounds[i] * new_coeff;
1329 } else {
1330 // Add new_coeff * (UB - X)
1331 cut_.coeffs.push_back(-new_coeff);
1332 cut_.vars.push_back(base_ct.vars[i]);
1333 cut_.ub -= upper_bounds[i] * new_coeff;
1334 }
1335 }
1336
1337 if (scaling > 1) DivideByGCD(&cut_);
1338 return true;
1339}
1340
1342 IntegerVariable x,
1343 IntegerVariable y,
1344 int linearization_level,
1345 Model* model) {
1346 CutGenerator result;
1347 result.vars = {z, x, y};
1348
1349 IntegerTrail* const integer_trail = model->GetOrCreate<IntegerTrail>();
1350 Trail* trail = model->GetOrCreate<Trail>();
1351
1352 result.generate_cuts =
1353 [z, x, y, linearization_level, model, trail, integer_trail](
1355 LinearConstraintManager* manager) {
1356 if (trail->CurrentDecisionLevel() > 0 && linearization_level == 1) {
1357 return true;
1358 }
1359 const int64_t x_lb = integer_trail->LevelZeroLowerBound(x).value();
1360 const int64_t x_ub = integer_trail->LevelZeroUpperBound(x).value();
1361 const int64_t y_lb = integer_trail->LevelZeroLowerBound(y).value();
1362 const int64_t y_ub = integer_trail->LevelZeroUpperBound(y).value();
1363
1364 // TODO(user): Compute a better bound (int_max / 4 ?).
1365 const int64_t kMaxSafeInteger = (int64_t{1} << 53) - 1;
1366
1367 if (CapProd(x_ub, y_ub) >= kMaxSafeInteger) {
1368 VLOG(3) << "Potential overflow in PositiveMultiplicationCutGenerator";
1369 return true;
1370 }
1371
1372 const double x_lp_value = lp_values[x];
1373 const double y_lp_value = lp_values[y];
1374 const double z_lp_value = lp_values[z];
1375
1376 // TODO(user): As the bounds change monotonically, these cuts
1377 // dominate any previous one. try to keep a reference to the cut and
1378 // replace it. Alternatively, add an API for a level-zero bound change
1379 // callback.
1380
1381 // Cut -z + x_coeff * x + y_coeff* y <= rhs
1382 auto try_add_above_cut =
1383 [manager, z_lp_value, x_lp_value, y_lp_value, x, y, z, model,
1384 &lp_values](int64_t x_coeff, int64_t y_coeff, int64_t rhs) {
1385 if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff >=
1386 rhs + kMinCutViolation) {
1388 /*ub=*/IntegerValue(rhs));
1389 cut.AddTerm(z, IntegerValue(-1));
1390 if (x_coeff != 0) cut.AddTerm(x, IntegerValue(x_coeff));
1391 if (y_coeff != 0) cut.AddTerm(y, IntegerValue(y_coeff));
1392 manager->AddCut(cut.Build(), "PositiveProduct", lp_values);
1393 }
1394 };
1395
1396 // Cut -z + x_coeff * x + y_coeff* y >= rhs
1397 auto try_add_below_cut =
1398 [manager, z_lp_value, x_lp_value, y_lp_value, x, y, z, model,
1399 &lp_values](int64_t x_coeff, int64_t y_coeff, int64_t rhs) {
1400 if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff <=
1401 rhs - kMinCutViolation) {
1402 LinearConstraintBuilder cut(model, /*lb=*/IntegerValue(rhs),
1403 /*ub=*/kMaxIntegerValue);
1404 cut.AddTerm(z, IntegerValue(-1));
1405 if (x_coeff != 0) cut.AddTerm(x, IntegerValue(x_coeff));
1406 if (y_coeff != 0) cut.AddTerm(y, IntegerValue(y_coeff));
1407 manager->AddCut(cut.Build(), "PositiveProduct", lp_values);
1408 }
1409 };
1410
1411 // McCormick relaxation of bilinear constraints. These 4 cuts are the
1412 // exact facets of the x * y polyhedron for a bounded x and y.
1413 //
1414 // Each cut correspond to plane that contains two of the line
1415 // (x=x_lb), (x=x_ub), (y=y_lb), (y=y_ub). The easiest to
1416 // understand them is to draw the x*y curves and see the 4
1417 // planes that correspond to the convex hull of the graph.
1418 try_add_above_cut(y_lb, x_lb, x_lb * y_lb);
1419 try_add_above_cut(y_ub, x_ub, x_ub * y_ub);
1420 try_add_below_cut(y_ub, x_lb, x_lb * y_ub);
1421 try_add_below_cut(y_lb, x_ub, x_ub * y_lb);
1422 return true;
1423 };
1424
1425 return result;
1426}
1427
1428CutGenerator CreateSquareCutGenerator(IntegerVariable y, IntegerVariable x,
1429 int linearization_level, Model* model) {
1430 CutGenerator result;
1431 result.vars = {y, x};
1432
1433 Trail* trail = model->GetOrCreate<Trail>();
1434 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1435 result.generate_cuts =
1436 [y, x, linearization_level, trail, integer_trail](
1438 LinearConstraintManager* manager) {
1439 if (trail->CurrentDecisionLevel() > 0 && linearization_level == 1) {
1440 return true;
1441 }
1442 const int64_t x_ub = integer_trail->LevelZeroUpperBound(x).value();
1443 const int64_t x_lb = integer_trail->LevelZeroLowerBound(x).value();
1444
1445 if (x_lb == x_ub) return true;
1446
1447 // Check for potential overflows.
1448 if (x_ub > (int64_t{1} << 31)) return true;
1449 DCHECK_GE(x_lb, 0);
1450
1451 const double y_lp_value = lp_values[y];
1452 const double x_lp_value = lp_values[x];
1453
1454 // First cut: target should be below the line:
1455 // (x_lb, x_lb ^ 2) to (x_ub, x_ub ^ 2).
1456 // The slope of that line is (ub^2 - lb^2) / (ub - lb) = ub + lb.
1457 const int64_t y_lb = x_lb * x_lb;
1458 const int64_t above_slope = x_ub + x_lb;
1459 const double max_lp_y = y_lb + above_slope * (x_lp_value - x_lb);
1460 if (y_lp_value >= max_lp_y + kMinCutViolation) {
1461 // cut: y <= (x_lb + x_ub) * x - x_lb * x_ub
1462 LinearConstraint above_cut;
1463 above_cut.vars.push_back(y);
1464 above_cut.coeffs.push_back(IntegerValue(1));
1465 above_cut.vars.push_back(x);
1466 above_cut.coeffs.push_back(IntegerValue(-above_slope));
1467 above_cut.lb = kMinIntegerValue;
1468 above_cut.ub = IntegerValue(-x_lb * x_ub);
1469 manager->AddCut(above_cut, "SquareUpper", lp_values);
1470 }
1471
1472 // Second cut: target should be above all the lines
1473 // (value, value ^ 2) to (value + 1, (value + 1) ^ 2)
1474 // The slope of that line is 2 * value + 1
1475 //
1476 // Note that we only add one of these cuts. The one for x_lp_value in
1477 // [value, value + 1].
1478 const int64_t x_floor = static_cast<int64_t>(std::floor(x_lp_value));
1479 const int64_t below_slope = 2 * x_floor + 1;
1480 const double min_lp_y =
1481 below_slope * x_lp_value - x_floor - x_floor * x_floor;
1482 if (min_lp_y >= y_lp_value + kMinCutViolation) {
1483 // cut: y >= below_slope * (x - x_floor) + x_floor ^ 2
1484 // : y >= below_slope * x - x_floor ^ 2 - x_floor
1485 LinearConstraint below_cut;
1486 below_cut.vars.push_back(y);
1487 below_cut.coeffs.push_back(IntegerValue(1));
1488 below_cut.vars.push_back(x);
1489 below_cut.coeffs.push_back(-IntegerValue(below_slope));
1490 below_cut.lb = IntegerValue(-x_floor - x_floor * x_floor);
1491 below_cut.ub = kMaxIntegerValue;
1492 manager->AddCut(below_cut, "SquareLower", lp_values);
1493 }
1494 return true;
1495 };
1496
1497 return result;
1498}
1499
1500void ImpliedBoundsProcessor::ProcessUpperBoundedConstraint(
1502 LinearConstraint* cut) {
1503 ProcessUpperBoundedConstraintWithSlackCreation(
1504 /*substitute_only_inner_variables=*/false, IntegerVariable(0), lp_values,
1505 cut, nullptr);
1506}
1507
1509ImpliedBoundsProcessor::GetCachedImpliedBoundInfo(IntegerVariable var) {
1510 auto it = cache_.find(var);
1511 if (it != cache_.end()) return it->second;
1512 return BestImpliedBoundInfo();
1513}
1514
1516ImpliedBoundsProcessor::ComputeBestImpliedBound(
1517 IntegerVariable var,
1519 auto it = cache_.find(var);
1520 if (it != cache_.end()) return it->second;
1521 BestImpliedBoundInfo result;
1522 const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
1523 for (const ImpliedBoundEntry& entry :
1524 implied_bounds_->GetImpliedBounds(var)) {
1525 // Only process entries with a Boolean variable currently part of the LP
1526 // we are considering for this cut.
1527 //
1528 // TODO(user): the more we use cuts, the less it make sense to have a
1529 // lot of small independent LPs.
1530 if (!lp_vars_.contains(PositiveVariable(entry.literal_view))) {
1531 continue;
1532 }
1533
1534 // The equation is X = lb + diff * Bool + Slack where Bool is in [0, 1]
1535 // and slack in [0, ub - lb].
1536 const IntegerValue diff = entry.lower_bound - lb;
1537 CHECK_GE(diff, 0);
1538 const double bool_lp_value = entry.is_positive
1539 ? lp_values[entry.literal_view]
1540 : 1.0 - lp_values[entry.literal_view];
1541 const double slack_lp_value =
1542 lp_values[var] - ToDouble(lb) - bool_lp_value * ToDouble(diff);
1543
1544 // If the implied bound equation is not respected, we just add it
1545 // to implied_bound_cuts, and skip the entry for now.
1546 if (slack_lp_value < -1e-4) {
1547 LinearConstraint ib_cut;
1548 ib_cut.lb = kMinIntegerValue;
1549 std::vector<std::pair<IntegerVariable, IntegerValue>> terms;
1550 if (entry.is_positive) {
1551 // X >= Indicator * (bound - lb) + lb
1552 terms.push_back({entry.literal_view, diff});
1553 terms.push_back({var, IntegerValue(-1)});
1554 ib_cut.ub = -lb;
1555 } else {
1556 // X >= -Indicator * (bound - lb) + bound
1557 terms.push_back({entry.literal_view, -diff});
1558 terms.push_back({var, IntegerValue(-1)});
1559 ib_cut.ub = -entry.lower_bound;
1560 }
1561 CleanTermsAndFillConstraint(&terms, &ib_cut);
1562 ib_cut_pool_.AddCut(std::move(ib_cut), "IB", lp_values);
1563 continue;
1564 }
1565
1566 // We look for tight implied bounds, and amongst the tightest one, we
1567 // prefer larger coefficient in front of the Boolean.
1568 if (slack_lp_value + 1e-4 < result.slack_lp_value ||
1569 (slack_lp_value < result.slack_lp_value + 1e-4 &&
1570 diff > result.bound_diff)) {
1571 result.bool_lp_value = bool_lp_value;
1572 result.slack_lp_value = slack_lp_value;
1573
1574 result.bound_diff = diff;
1575 result.is_positive = entry.is_positive;
1576 result.bool_var = entry.literal_view;
1577 }
1578 }
1579 cache_[var] = result;
1580 return result;
1581}
1582
1583void ImpliedBoundsProcessor::RecomputeCacheAndSeparateSomeImpliedBoundCuts(
1585 cache_.clear();
1586 for (const IntegerVariable var :
1587 implied_bounds_->VariablesWithImpliedBounds()) {
1588 if (!lp_vars_.contains(PositiveVariable(var))) continue;
1589 ComputeBestImpliedBound(var, lp_values);
1590 }
1591}
1592
1593void ImpliedBoundsProcessor::ProcessUpperBoundedConstraintWithSlackCreation(
1594 bool substitute_only_inner_variables, IntegerVariable first_slack,
1596 LinearConstraint* cut, std::vector<SlackInfo>* slack_infos) {
1597 if (cache_.empty()) return; // Nothing to do.
1598 tmp_terms_.clear();
1599 IntegerValue new_ub = cut->ub;
1600 bool changed = false;
1601
1602 // TODO(user): we could relax a bit this test.
1603 int64_t overflow_detection = 0;
1604
1605 const int size = cut->vars.size();
1606 for (int i = 0; i < size; ++i) {
1607 IntegerVariable var = cut->vars[i];
1608 IntegerValue coeff = cut->coeffs[i];
1609
1610 // Starts by positive coefficient.
1611 // TODO(user): Not clear this is best.
1612 if (coeff < 0) {
1613 coeff = -coeff;
1614 var = NegationOf(var);
1615 }
1616
1617 // Find the best implied bound to use.
1618 // TODO(user): We could also use implied upper bound, that is try with
1619 // NegationOf(var).
1620 const BestImpliedBoundInfo info = GetCachedImpliedBoundInfo(var);
1621 const int old_size = tmp_terms_.size();
1622
1623 // Shall we keep the original term ?
1624 bool keep_term = false;
1625 if (info.bool_var == kNoIntegerVariable) keep_term = true;
1626 if (CapProd(std::abs(coeff.value()), info.bound_diff.value()) ==
1628 keep_term = true;
1629 }
1630
1631 // TODO(user): On some problem, not replacing the variable at their bound
1632 // by an implied bounds seems beneficial. This is especially the case on
1633 // g200x740.mps.gz
1634 //
1635 // Note that in ComputeCut() the variable with an LP value at the bound do
1636 // not contribute to the cut efficacity (no loss) but do contribute to the
1637 // various heuristic based on the coefficient magnitude.
1638 if (substitute_only_inner_variables) {
1639 const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
1640 const IntegerValue ub = integer_trail_->LevelZeroUpperBound(var);
1641 if (lp_values[var] - ToDouble(lb) < 1e-2) keep_term = true;
1642 if (ToDouble(ub) - lp_values[var] < 1e-2) keep_term = true;
1643 }
1644
1645 // This is when we do not add slack.
1646 if (slack_infos == nullptr) {
1647 // We do not want to loose anything, so we only replace if the slack lp is
1648 // zero.
1649 if (info.slack_lp_value > 1e-6) keep_term = true;
1650 }
1651
1652 if (keep_term) {
1653 tmp_terms_.push_back({var, coeff});
1654 } else {
1655 // Substitute.
1656 const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
1657 const IntegerValue ub = integer_trail_->LevelZeroUpperBound(var);
1658
1659 SlackInfo slack_info;
1660 slack_info.lp_value = info.slack_lp_value;
1661 slack_info.lb = 0;
1662 slack_info.ub = ub - lb;
1663
1664 if (info.is_positive) {
1665 // X = Indicator * diff + lb + Slack
1666 tmp_terms_.push_back({info.bool_var, coeff * info.bound_diff});
1667 if (!AddProductTo(-coeff, lb, &new_ub)) {
1668 VLOG(2) << "Overflow";
1669 return;
1670 }
1671 if (slack_infos != nullptr) {
1672 tmp_terms_.push_back({first_slack, coeff});
1673 first_slack += 2;
1674
1675 // slack = X - Indicator * info.bound_diff - lb;
1676 slack_info.terms.push_back({var, IntegerValue(1)});
1677 slack_info.terms.push_back({info.bool_var, -info.bound_diff});
1678 slack_info.offset = -lb;
1679 slack_infos->push_back(slack_info);
1680 }
1681 } else {
1682 // X = (1 - Indicator) * (diff) + lb + Slack
1683 // X = -Indicator * (diff) + lb + diff + Slack
1684 tmp_terms_.push_back({info.bool_var, -coeff * info.bound_diff});
1685 if (!AddProductTo(-coeff, lb + info.bound_diff, &new_ub)) {
1686 VLOG(2) << "Overflow";
1687 return;
1688 }
1689 if (slack_infos != nullptr) {
1690 tmp_terms_.push_back({first_slack, coeff});
1691 first_slack += 2;
1692
1693 // slack = X + Indicator * info.bound_diff - lb - diff;
1694 slack_info.terms.push_back({var, IntegerValue(1)});
1695 slack_info.terms.push_back({info.bool_var, +info.bound_diff});
1696 slack_info.offset = -lb - info.bound_diff;
1697 slack_infos->push_back(slack_info);
1698 }
1699 }
1700 changed = true;
1701 }
1702
1703 // Add all the new terms coefficient to the overflow detection to avoid
1704 // issue when merging terms referring to the same variable.
1705 for (int i = old_size; i < tmp_terms_.size(); ++i) {
1706 overflow_detection =
1707 CapAdd(overflow_detection, std::abs(tmp_terms_[i].second.value()));
1708 }
1709 }
1710
1711 if (overflow_detection >= kMaxIntegerValue) {
1712 VLOG(2) << "Overflow";
1713 return;
1714 }
1715 if (!changed) return;
1716
1717 // Update the cut.
1718 //
1719 // Note that because of our overflow_detection variable, there should be
1720 // no integer overflow when we merge identical terms.
1721 cut->lb = kMinIntegerValue; // Not relevant.
1722 cut->ub = new_ub;
1723 CleanTermsAndFillConstraint(&tmp_terms_, cut);
1724}
1725
1726bool ImpliedBoundsProcessor::DebugSlack(IntegerVariable first_slack,
1727 const LinearConstraint& initial_cut,
1728 const LinearConstraint& cut,
1729 const std::vector<SlackInfo>& info) {
1730 tmp_terms_.clear();
1731 IntegerValue new_ub = cut.ub;
1732 for (int i = 0; i < cut.vars.size(); ++i) {
1733 // Simple copy for non-slack variables.
1734 if (cut.vars[i] < first_slack) {
1735 tmp_terms_.push_back({cut.vars[i], cut.coeffs[i]});
1736 continue;
1737 }
1738
1739 // Replace slack by its definition.
1740 const IntegerValue multiplier = cut.coeffs[i];
1741 const int index = (cut.vars[i].value() - first_slack.value()) / 2;
1742 for (const std::pair<IntegerVariable, IntegerValue>& term :
1743 info[index].terms) {
1744 tmp_terms_.push_back({term.first, term.second * multiplier});
1745 }
1746 new_ub -= multiplier * info[index].offset;
1747 }
1748
1749 LinearConstraint tmp_cut;
1750 tmp_cut.lb = kMinIntegerValue; // Not relevant.
1751 tmp_cut.ub = new_ub;
1752 CleanTermsAndFillConstraint(&tmp_terms_, &tmp_cut);
1753 MakeAllVariablesPositive(&tmp_cut);
1754
1755 // We need to canonicalize the initial_cut too for comparison. Note that we
1756 // only use this for debug, so we don't care too much about the memory and
1757 // extra time.
1758 // TODO(user): Expose CanonicalizeConstraint() from the manager.
1759 LinearConstraint tmp_copy;
1760 tmp_terms_.clear();
1761 for (int i = 0; i < initial_cut.vars.size(); ++i) {
1762 tmp_terms_.push_back({initial_cut.vars[i], initial_cut.coeffs[i]});
1763 }
1764 tmp_copy.lb = kMinIntegerValue; // Not relevant.
1765 tmp_copy.ub = new_ub;
1766 CleanTermsAndFillConstraint(&tmp_terms_, &tmp_copy);
1767 MakeAllVariablesPositive(&tmp_copy);
1768
1769 if (tmp_cut == tmp_copy) return true;
1770
1771 LOG(INFO) << first_slack;
1772 LOG(INFO) << tmp_copy.DebugString();
1773 LOG(INFO) << cut.DebugString();
1774 LOG(INFO) << tmp_cut.DebugString();
1775 return false;
1776}
1777
1778namespace {
1779
1780void TryToGenerateAllDiffCut(
1781 const std::vector<std::pair<double, IntegerVariable>>& sorted_vars_lp,
1782 const IntegerTrail& integer_trail,
1784 LinearConstraintManager* manager) {
1785 Domain current_union;
1786 std::vector<IntegerVariable> current_set_vars;
1787 double sum = 0.0;
1788 for (auto value_var : sorted_vars_lp) {
1789 sum += value_var.first;
1790 const IntegerVariable var = value_var.second;
1791 // TODO(user): The union of the domain of the variable being considered
1792 // does not give the tightest bounds, try to get better bounds.
1793 current_union =
1794 current_union.UnionWith(integer_trail.InitialVariableDomain(var));
1795 current_set_vars.push_back(var);
1796 const int64_t required_min_sum =
1797 SumOfKMinValueInDomain(current_union, current_set_vars.size());
1798 const int64_t required_max_sum =
1799 SumOfKMaxValueInDomain(current_union, current_set_vars.size());
1800 if (sum < required_min_sum || sum > required_max_sum) {
1801 LinearConstraint cut;
1802 for (IntegerVariable var : current_set_vars) {
1803 cut.AddTerm(var, IntegerValue(1));
1804 }
1805 cut.lb = IntegerValue(required_min_sum);
1806 cut.ub = IntegerValue(required_max_sum);
1807 manager->AddCut(cut, "all_diff", lp_values);
1808 // NOTE: We can extend the current set but it is more helpful to generate
1809 // the cut on a different set of variables so we reset the counters.
1810 sum = 0.0;
1811 current_set_vars.clear();
1812 current_union = Domain();
1813 }
1814 }
1815}
1816
1817} // namespace
1818
1820 const std::vector<IntegerVariable>& vars, Model* model) {
1821 CutGenerator result;
1822 result.vars = vars;
1823 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1824 Trail* trail = model->GetOrCreate<Trail>();
1825 result.generate_cuts =
1826 [vars, integer_trail, trail](
1828 LinearConstraintManager* manager) {
1829 // These cuts work at all levels but the generator adds too many cuts on
1830 // some instances and degrade the performance so we only use it at level
1831 // 0.
1832 if (trail->CurrentDecisionLevel() > 0) return true;
1833 std::vector<std::pair<double, IntegerVariable>> sorted_vars;
1834 for (const IntegerVariable var : vars) {
1835 if (integer_trail->LevelZeroLowerBound(var) ==
1836 integer_trail->LevelZeroUpperBound(var)) {
1837 continue;
1838 }
1839 sorted_vars.push_back(std::make_pair(lp_values[var], var));
1840 }
1841 std::sort(sorted_vars.begin(), sorted_vars.end());
1842 TryToGenerateAllDiffCut(sorted_vars, *integer_trail, lp_values,
1843 manager);
1844 // Other direction.
1845 std::reverse(sorted_vars.begin(), sorted_vars.end());
1846 TryToGenerateAllDiffCut(sorted_vars, *integer_trail, lp_values,
1847 manager);
1848 return true;
1849 };
1850 VLOG(1) << "Created all_diff cut generator of size: " << vars.size();
1851 return result;
1852}
1853
1854namespace {
1855// Returns max((w2i - w1i)*Li, (w2i - w1i)*Ui).
1856IntegerValue MaxCornerDifference(const IntegerVariable var,
1857 const IntegerValue w1_i,
1858 const IntegerValue w2_i,
1859 const IntegerTrail& integer_trail) {
1860 const IntegerValue lb = integer_trail.LevelZeroLowerBound(var);
1861 const IntegerValue ub = integer_trail.LevelZeroUpperBound(var);
1862 return std::max((w2_i - w1_i) * lb, (w2_i - w1_i) * ub);
1863}
1864
1865// This is the coefficient of zk in the cut, where k = max_index.
1866// MPlusCoefficient_ki = max((wki - wI(i)i) * Li,
1867// (wki - wI(i)i) * Ui)
1868// = max corner difference for variable i,
1869// target expr I(i), max expr k.
1870// The coefficient of zk is Sum(i=1..n)(MPlusCoefficient_ki) + bk
1871IntegerValue MPlusCoefficient(
1872 const std::vector<IntegerVariable>& x_vars,
1873 const std::vector<LinearExpression>& exprs,
1874 const absl::StrongVector<IntegerVariable, int>& variable_partition,
1875 const int max_index, const IntegerTrail& integer_trail) {
1876 IntegerValue coeff = exprs[max_index].offset;
1877 // TODO(user): This algo is quadratic since GetCoefficientOfPositiveVar()
1878 // is linear. This can be optimized (better complexity) if needed.
1879 for (const IntegerVariable var : x_vars) {
1880 const int target_index = variable_partition[var];
1881 if (max_index != target_index) {
1882 coeff += MaxCornerDifference(
1883 var, GetCoefficientOfPositiveVar(var, exprs[target_index]),
1884 GetCoefficientOfPositiveVar(var, exprs[max_index]), integer_trail);
1885 }
1886 }
1887 return coeff;
1888}
1889
1890// Compute the value of
1891// rhs = wI(i)i * xi + Sum(k=1..d)(MPlusCoefficient_ki * zk)
1892// for variable xi for given target index I(i).
1893double ComputeContribution(
1894 const IntegerVariable xi_var, const std::vector<IntegerVariable>& z_vars,
1895 const std::vector<LinearExpression>& exprs,
1897 const IntegerTrail& integer_trail, const int target_index) {
1898 CHECK_GE(target_index, 0);
1899 CHECK_LT(target_index, exprs.size());
1900 const LinearExpression& target_expr = exprs[target_index];
1901 const double xi_value = lp_values[xi_var];
1902 const IntegerValue wt_i = GetCoefficientOfPositiveVar(xi_var, target_expr);
1903 double contrib = ToDouble(wt_i) * xi_value;
1904 for (int expr_index = 0; expr_index < exprs.size(); ++expr_index) {
1905 if (expr_index == target_index) continue;
1906 const LinearExpression& max_expr = exprs[expr_index];
1907 const double z_max_value = lp_values[z_vars[expr_index]];
1908 const IntegerValue corner_value = MaxCornerDifference(
1909 xi_var, wt_i, GetCoefficientOfPositiveVar(xi_var, max_expr),
1910 integer_trail);
1911 contrib += ToDouble(corner_value) * z_max_value;
1912 }
1913 return contrib;
1914}
1915} // namespace
1916
1918 const IntegerVariable target, const std::vector<LinearExpression>& exprs,
1919 const std::vector<IntegerVariable>& z_vars, Model* model) {
1920 CutGenerator result;
1921 std::vector<IntegerVariable> x_vars;
1922 result.vars = {target};
1923 const int num_exprs = exprs.size();
1924 for (int i = 0; i < num_exprs; ++i) {
1925 result.vars.push_back(z_vars[i]);
1926 x_vars.insert(x_vars.end(), exprs[i].vars.begin(), exprs[i].vars.end());
1927 }
1929 // All expressions should only contain positive variables.
1930 DCHECK(std::all_of(x_vars.begin(), x_vars.end(), [](IntegerVariable var) {
1931 return VariableIsPositive(var);
1932 }));
1933 result.vars.insert(result.vars.end(), x_vars.begin(), x_vars.end());
1934
1935 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1936 result.generate_cuts =
1937 [x_vars, z_vars, target, num_exprs, exprs, integer_trail, model](
1939 LinearConstraintManager* manager) {
1941 lp_values.size(), -1);
1942 absl::StrongVector<IntegerVariable, double> variable_partition_contrib(
1943 lp_values.size(), std::numeric_limits<double>::infinity());
1944 for (int expr_index = 0; expr_index < num_exprs; ++expr_index) {
1945 for (const IntegerVariable var : x_vars) {
1946 const double contribution = ComputeContribution(
1947 var, z_vars, exprs, lp_values, *integer_trail, expr_index);
1948 const double prev_contribution = variable_partition_contrib[var];
1949 if (contribution < prev_contribution) {
1950 variable_partition[var] = expr_index;
1951 variable_partition_contrib[var] = contribution;
1952 }
1953 }
1954 }
1955
1956 LinearConstraintBuilder cut(model, /*lb=*/IntegerValue(0),
1957 /*ub=*/kMaxIntegerValue);
1958 double violation = lp_values[target];
1959 cut.AddTerm(target, IntegerValue(-1));
1960
1961 for (const IntegerVariable xi_var : x_vars) {
1962 const int input_index = variable_partition[xi_var];
1963 const LinearExpression& expr = exprs[input_index];
1964 const IntegerValue coeff = GetCoefficientOfPositiveVar(xi_var, expr);
1965 if (coeff != IntegerValue(0)) {
1966 cut.AddTerm(xi_var, coeff);
1967 }
1968 violation -= ToDouble(coeff) * lp_values[xi_var];
1969 }
1970 for (int expr_index = 0; expr_index < num_exprs; ++expr_index) {
1971 const IntegerVariable z_var = z_vars[expr_index];
1972 const IntegerValue z_coeff = MPlusCoefficient(
1973 x_vars, exprs, variable_partition, expr_index, *integer_trail);
1974 if (z_coeff != IntegerValue(0)) {
1975 cut.AddTerm(z_var, z_coeff);
1976 }
1977 violation -= ToDouble(z_coeff) * lp_values[z_var];
1978 }
1979 if (violation > 1e-2) {
1980 manager->AddCut(cut.Build(), "LinMax", lp_values);
1981 }
1982 return true;
1983 };
1984 return result;
1985}
1986
1987namespace {
1988
1989IntegerValue EvaluateMaxAffine(
1990 const std::vector<std::pair<IntegerValue, IntegerValue>>& affines,
1991 IntegerValue x) {
1992 IntegerValue y = kMinIntegerValue;
1993 for (const auto& p : affines) {
1994 y = std::max(y, x * p.first + p.second);
1995 }
1996 return y;
1997}
1998
1999} // namespace
2000
2002 const LinearExpression& target, IntegerVariable var,
2003 const std::vector<std::pair<IntegerValue, IntegerValue>>& affines,
2004 Model* model) {
2005 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
2006 const IntegerValue x_min = integer_trail->LevelZeroLowerBound(var);
2007 const IntegerValue x_max = integer_trail->LevelZeroUpperBound(var);
2008
2009 const IntegerValue y_at_min = EvaluateMaxAffine(affines, x_min);
2010 const IntegerValue y_at_max = EvaluateMaxAffine(affines, x_max);
2011
2012 // TODO(user): Be careful to not have any integer overflow in any of
2013 // the formula used here.
2014 const IntegerValue delta_x = x_max - x_min;
2015 const IntegerValue delta_y = y_at_max - y_at_min;
2016
2017 // target <= y_at_min + (delta_y / delta_x) * (var - x_min)
2018 // delta_x * target <= delta_x * y_at_min + delta_y * (var - x_min)
2019 // -delta_y * var + delta_x * target <= delta_x * y_at_min - delta_y * x_min
2020 const IntegerValue rhs = delta_x * y_at_min - delta_y * x_min;
2022 lc.AddLinearExpression(target, delta_x);
2023 lc.AddTerm(var, -delta_y);
2024 LinearConstraint ct = lc.Build();
2025
2026 // Prevent to create constraints that can overflow.
2027 if (!ValidateLinearConstraintForOverflow(ct, *integer_trail)) {
2028 VLOG(2) << "Linear constraint can cause overflow: " << ct;
2029
2030 // TODO(user): Change API instead of returning trivial constraint?
2031 ct.Clear();
2032 }
2033
2034 return ct;
2035}
2036
2038 LinearExpression target, IntegerVariable var,
2039 std::vector<std::pair<IntegerValue, IntegerValue>> affines,
2040 const std::string cut_name, Model* model) {
2041 CutGenerator result;
2042 result.vars = target.vars;
2043 result.vars.push_back(var);
2045
2046 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
2047 result.generate_cuts =
2048 [target, var, affines, cut_name, integer_trail, model](
2050 LinearConstraintManager* manager) {
2051 if (integer_trail->IsFixed(var)) return true;
2052 manager->AddCut(BuildMaxAffineUpConstraint(target, var, affines, model),
2053 cut_name, lp_values);
2054 return true;
2055 };
2056 return result;
2057}
2058
2060 const std::vector<IntegerVariable>& base_variables, Model* model) {
2061 // Filter base_variables to only keep the one with a literal view, and
2062 // do the conversion.
2063 std::vector<IntegerVariable> variables;
2064 std::vector<Literal> literals;
2065 absl::flat_hash_map<LiteralIndex, IntegerVariable> positive_map;
2066 absl::flat_hash_map<LiteralIndex, IntegerVariable> negative_map;
2067 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
2068 auto* encoder = model->GetOrCreate<IntegerEncoder>();
2069 for (const IntegerVariable var : base_variables) {
2070 if (integer_trail->LowerBound(var) != IntegerValue(0)) continue;
2071 if (integer_trail->UpperBound(var) != IntegerValue(1)) continue;
2072 const LiteralIndex literal_index = encoder->GetAssociatedLiteral(
2073 IntegerLiteral::GreaterOrEqual(var, IntegerValue(1)));
2074 if (literal_index != kNoLiteralIndex) {
2075 variables.push_back(var);
2076 literals.push_back(Literal(literal_index));
2077 positive_map[literal_index] = var;
2078 negative_map[Literal(literal_index).NegatedIndex()] = var;
2079 }
2080 }
2081 CutGenerator result;
2082 result.vars = variables;
2083 auto* implication_graph = model->GetOrCreate<BinaryImplicationGraph>();
2084 result.generate_cuts =
2085 [variables, literals, implication_graph, positive_map, negative_map,
2087 LinearConstraintManager* manager) {
2088 std::vector<double> packed_values;
2089 for (int i = 0; i < literals.size(); ++i) {
2090 packed_values.push_back(lp_values[variables[i]]);
2091 }
2092 const std::vector<std::vector<Literal>> at_most_ones =
2093 implication_graph->GenerateAtMostOnesWithLargeWeight(literals,
2094 packed_values);
2095
2096 for (const std::vector<Literal>& at_most_one : at_most_ones) {
2097 // We need to express such "at most one" in term of the initial
2098 // variables, so we do not use the
2099 // LinearConstraintBuilder::AddLiteralTerm() here.
2102 IntegerValue(1));
2103 for (const Literal l : at_most_one) {
2104 if (ContainsKey(positive_map, l.Index())) {
2105 builder.AddTerm(positive_map.at(l.Index()), IntegerValue(1));
2106 } else {
2107 // Add 1 - X to the linear constraint.
2108 builder.AddTerm(negative_map.at(l.Index()), IntegerValue(-1));
2109 builder.AddConstant(IntegerValue(1));
2110 }
2111 }
2112
2113 manager->AddCut(builder.Build(), "clique", lp_values);
2114 }
2115 return true;
2116 };
2117 return result;
2118}
2119
2120} // namespace sat
2121} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:491
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:887
#define CHECK_LT(val1, val2)
Definition: base/logging.h:701
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
#define CHECK_GE(val1, val2)
Definition: base/logging.h:702
#define CHECK_GT(val1, val2)
Definition: base/logging.h:703
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:890
#define CHECK_NE(val1, val2)
Definition: base/logging.h:699
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:889
#define LOG(severity)
Definition: base/logging.h:416
#define DCHECK(condition)
Definition: base/logging.h:885
#define VLOG(verboselevel)
Definition: base/logging.h:979
We call domain any subset of Int64 = [kint64min, kint64max].
Domain UnionWith(const Domain &domain) const
Returns the union of D and domain.
void Init(const std::vector< double > &profits, const std::vector< double > &weights, const double capacity)
double Solve(TimeLimit *time_limit, bool *is_solution_optimal)
void set_solution_upper_bound_threshold(const double solution_upper_bound_threshold)
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:533
bool TrySimpleKnapsack(const LinearConstraint base_ct, const std::vector< double > &lp_values, const std::vector< IntegerValue > &lower_bounds, const std::vector< IntegerValue > &upper_bounds)
Definition: cuts.cc:1168
void ProcessUpperBoundedConstraint(const absl::StrongVector< IntegerVariable, double > &lp_values, LinearConstraint *cut)
Definition: cuts.cc:1500
void RecomputeCacheAndSeparateSomeImpliedBoundCuts(const absl::StrongVector< IntegerVariable, double > &lp_values)
Definition: cuts.cc:1583
BestImpliedBoundInfo GetCachedImpliedBoundInfo(IntegerVariable var)
Definition: cuts.cc:1509
void ComputeCut(RoundingOptions options, const std::vector< double > &lp_values, const std::vector< IntegerValue > &lower_bounds, const std::vector< IntegerValue > &upper_bounds, ImpliedBoundsProcessor *ib_processor, LinearConstraint *cut)
Definition: cuts.cc:720
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1353
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1412
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1407
const Domain & InitialVariableDomain(IntegerVariable var) const
Definition: integer.cc:682
void AddLinearExpression(const LinearExpression &expr)
void AddTerm(IntegerVariable var, IntegerValue coeff)
bool AddCut(LinearConstraint ct, std::string type_name, const absl::StrongVector< IntegerVariable, double > &lp_solution, std::string extra_info="")
LiteralIndex NegatedIndex() const
Definition: sat_base.h:86
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
int64_t b
int64_t a
ModelSharedTimeLimit * time_limit
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
double upper_bound
GRBmodel * model
const int INFO
Definition: log_severity.h:31
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:200
static double ToDouble(double f)
Definition: lp_types.h:69
void ConvertToKnapsackForm(const LinearConstraint &constraint, std::vector< LinearConstraint > *knapsack_constraints, IntegerTrail *integer_trail)
Definition: cuts.cc:393
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:91
bool ValidateLinearConstraintForOverflow(const LinearConstraint &constraint, const IntegerTrail &integer_trail)
bool AddProductTo(IntegerValue a, IntegerValue b, IntegerValue *result)
Definition: integer.h:114
void CleanTermsAndFillConstraint(std::vector< std::pair< IntegerVariable, IntegerValue > > *terms, LinearConstraint *constraint)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
CutGenerator CreateSquareCutGenerator(IntegerVariable y, IntegerVariable x, int linearization_level, Model *model)
Definition: cuts.cc:1428
LinearConstraint GetPreprocessedLinearConstraint(const LinearConstraint &constraint, const absl::StrongVector< IntegerVariable, double > &lp_values, const IntegerTrail &integer_trail)
Definition: cuts.cc:255
IntType IntTypeAbs(IntType t)
Definition: integer.h:78
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:82
const LiteralIndex kNoLiteralIndex(-1)
bool CanFormValidKnapsackCover(const LinearConstraint &preprocessed_constraint, const absl::StrongVector< IntegerVariable, double > &lp_values, const IntegerTrail &integer_trail)
Definition: cuts.cc:375
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
void RemoveZeroTerms(LinearConstraint *constraint)
IntegerValue GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue max_t)
Definition: cuts.cc:624
double GetKnapsackUpperBound(std::vector< KnapsackItem > items, const double capacity)
Definition: cuts.cc:323
bool CanBeFilteredUsingCutLowerBound(const LinearConstraint &preprocessed_constraint, const absl::StrongVector< IntegerVariable, double > &lp_values, const IntegerTrail &integer_trail)
Definition: cuts.cc:295
const IntegerVariable kNoIntegerVariable(-1)
void MakeAllCoefficientsPositive(LinearConstraint *constraint)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:142
CutGenerator CreateLinMaxCutGenerator(const IntegerVariable target, const std::vector< LinearExpression > &exprs, const std::vector< IntegerVariable > &z_vars, Model *model)
Definition: cuts.cc:1917
CutGenerator CreateAllDifferentCutGenerator(const std::vector< IntegerVariable > &vars, Model *model)
Definition: cuts.cc:1819
IntegerValue PositiveRemainder(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:106
LinearConstraint BuildMaxAffineUpConstraint(const LinearExpression &target, IntegerVariable var, const std::vector< std::pair< IntegerValue, IntegerValue > > &affines, Model *model)
Definition: cuts.cc:2001
bool CanBeFilteredUsingKnapsackUpperBound(const LinearConstraint &constraint, const absl::StrongVector< IntegerVariable, double > &lp_values, const IntegerTrail &integer_trail)
Definition: cuts.cc:341
std::function< IntegerValue(IntegerValue)> GetSuperAdditiveRoundingFunction(IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t, IntegerValue max_scaling)
Definition: cuts.cc:632
void MakeAllVariablesPositive(LinearConstraint *constraint)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:29
std::function< void(Model *)> GreaterOrEqual(IntegerVariable v, int64_t lb)
Definition: integer.h:1552
CutGenerator CreateMaxAffineCutGenerator(LinearExpression target, IntegerVariable var, std::vector< std::pair< IntegerValue, IntegerValue > > affines, const std::string cut_name, Model *model)
Definition: cuts.cc:2037
IntegerValue GetCoefficientOfPositiveVar(const IntegerVariable var, const LinearExpression &expr)
CutGenerator CreateKnapsackCoverCutGenerator(const std::vector< LinearConstraint > &base_constraints, const std::vector< IntegerVariable > &vars, Model *model)
Definition: cuts.cc:443
bool ConstraintIsTriviallyTrue(const LinearConstraint &constraint, const IntegerTrail &integer_trail)
Definition: cuts.cc:279
bool LiftKnapsackCut(const LinearConstraint &constraint, const absl::StrongVector< IntegerVariable, double > &lp_values, const std::vector< IntegerValue > &cut_vars_original_coefficients, const IntegerTrail &integer_trail, TimeLimit *time_limit, LinearConstraint *cut)
Definition: cuts.cc:176
CutGenerator CreateCliqueCutGenerator(const std::vector< IntegerVariable > &base_variables, Model *model)
Definition: cuts.cc:2059
void DivideByGCD(LinearConstraint *constraint)
double ComputeActivity(const LinearConstraint &constraint, const absl::StrongVector< IntegerVariable, double > &values)
double ToDouble(IntegerValue value)
Definition: integer.h:70
CutGenerator CreatePositiveMultiplicationCutGenerator(IntegerVariable z, IntegerVariable x, IntegerVariable y, int linearization_level, Model *model)
Definition: cuts.cc:1341
Collection of objects used to extend the Constraint Solver library.
int64_t SumOfKMinValueInDomain(const Domain &domain, int k)
int64_t CapAdd(int64_t x, int64_t y)
int64_t FloorRatio(int64_t value, int64_t positive_coeff)
int64_t CapSub(int64_t x, int64_t y)
int64_t SumOfKMaxValueInDomain(const Domain &domain, int k)
int64_t CapProd(int64_t x, int64_t y)
int64_t weight
Definition: pack.cc:510
int index
Definition: pack.cc:509
Fractional ratio
int64_t coefficient
int64_t capacity
std::vector< double > lower_bounds
std::vector< double > upper_bounds
std::vector< IntegerVariable > vars
Definition: cuts.h:43
std::function< bool(const absl::StrongVector< IntegerVariable, double > &lp_values, LinearConstraintManager *manager)> generate_cuts
Definition: cuts.h:47
std::vector< std::pair< IntegerVariable, IntegerValue > > terms
Definition: cuts.h:89
void AddTerm(IntegerVariable var, IntegerValue coeff)