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