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