From 7485b31723b1aee98b43160669793dffd3699bfa Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Wed, 30 Jun 2021 16:26:59 +0200 Subject: [PATCH] [CP-SAT] fix cuts; simplify LinearConstraintBuilder::AddLinearExpression signature --- ortools/sat/cuts.cc | 280 ++++++++++++++++--------------- ortools/sat/linear_constraint.cc | 23 +++ ortools/sat/linear_constraint.h | 81 ++++----- ortools/sat/linear_relaxation.cc | 15 +- 4 files changed, 219 insertions(+), 180 deletions(-) diff --git a/ortools/sat/cuts.cc b/ortools/sat/cuts.cc index 615b05bda8..c515a97403 100644 --- a/ortools/sat/cuts.cc +++ b/ortools/sat/cuts.cc @@ -2125,12 +2125,7 @@ GenerateCumulativeEnergyCuts(const std::string& cut_name, DCHECK(!demands.empty()); energy_lp += lp_values[demands[t]] * ToDouble(helper->SizeMin(t)); } else if (!energies.empty()) { - const LinearExpression& energy = energies[t]; - energy_lp += ToDouble(energy.offset); - for (int j = 0; j < energy.vars.size(); ++j) { - energy_lp += - lp_values[energy.vars[j]] * ToDouble(energy.coeffs[j]); - } + energy_lp += energies[t].LpValue(lp_values); } else { // demand and size are not fixed. DCHECK(!demands.empty()); energy_lp += @@ -2235,11 +2230,7 @@ GenerateCumulativeEnergyCuts(const std::string& cut_name, cut.AddTerm(demands[t], helper->SizeMin(t)); } else if (!energies.empty()) { // We favor the energy info instead of the McCormick relaxation. - const LinearExpression& energy = energies[t]; - cut.AddConstant(energy.offset); - for (int j = 0; j < energy.vars.size(); ++j) { - cut.AddTerm(energy.vars[j], energy.coeffs[j]); - } + cut.AddLinearExpression(energies[t]); use_energy = true; } else { // demand and size are not fixed. DCHECK(!demands.empty()); @@ -2326,6 +2317,7 @@ CutGenerator CreateCumulativeEnergyCutGenerator( result.vars.insert(result.vars.end(), energy.vars.begin(), energy.vars.end()); } + gtl::STLSortAndRemoveDuplicates(&result.vars); // TODO(user): Do not create the cut generator if the capacity is fixed, // all demands are fixed, and the intervals are always performed with a fixed @@ -2471,14 +2463,13 @@ CutGenerator CreateCumulativeOverlappingCutGenerator( return result; } +// Cached Information about one interval. struct PrecedenceEvent { IntegerValue start_min; IntegerValue start_max; - double start_lp; AffineExpression start; IntegerValue end_min; IntegerValue end_max; - double end_lp; AffineExpression end; IntegerValue demand_min; }; @@ -2497,12 +2488,12 @@ void GeneratePrecedenceCuts( (e1.start_min == e2.start_min && e1.end_max < e2.end_max); }); - const double epsilon = 1e-4; + const double tolerance = 1e-4; - for (int index1 = 0; index1 + 1 < num_events; ++index1) { - const PrecedenceEvent& e1 = events[index1]; - for (int index2 = index1 + 1; index2 < num_events; ++index2) { - const PrecedenceEvent& e2 = events[index2]; + for (int i = 0; i + 1 < num_events; ++i) { + const PrecedenceEvent& e1 = events[i]; + for (int j = i + 1; j < num_events; ++j) { + const PrecedenceEvent& e2 = events[j]; if (e2.start_min >= e1.end_max) break; // Break out of the index2 loop. // Encode only the interesting pairs. @@ -2512,13 +2503,15 @@ void GeneratePrecedenceCuts( const bool interval_2_can_precede_1 = e2.end_min <= e1.start_max; if (interval_1_can_precede_2 && !interval_2_can_precede_1 && - e1.end_lp >= e2.start_lp + epsilon) { + e1.end.LpValue(lp_values) >= + e2.start.LpValue(lp_values) + tolerance) { // interval1.end <= interval2.start LinearConstraintBuilder cut(model, kMinIntegerValue, IntegerValue(0)); cut.AddTerm(e1.end, IntegerValue(1)); cut.AddTerm(e2.start, IntegerValue(-1)); } else if (interval_2_can_precede_1 && !interval_1_can_precede_2 && - e2.end_lp >= e1.start_lp + epsilon) { + e2.end.LpValue(lp_values) >= + e1.start.LpValue(lp_values) + tolerance) { // interval2.end <= interval1.start LinearConstraintBuilder cut(model, kMinIntegerValue, IntegerValue(0)); cut.AddTerm(e2.end, IntegerValue(1)); @@ -2555,12 +2548,15 @@ CutGenerator CreateCumulativePrecedenceCutGenerator( std::vector events; for (int t = 0; t < helper->NumTasks(); ++t) { if (!helper->IsPresent(t)) continue; - events.push_back( - {helper->StartMin(t), helper->StartMax(t), - helper->Starts()[t].LpValue(lp_values), helper->Starts()[t], - helper->EndMin(t), helper->EndMax(t), - helper->Ends()[t].LpValue(lp_values), helper->Ends()[t], - integer_trail->LowerBound(demands[t])}); + PrecedenceEvent event; + event.start_min = helper->StartMin(t); + event.start_max = helper->StartMax(t); + event.start = helper->Starts()[t]; + event.end_min = helper->EndMin(t); + event.end_max = helper->EndMax(t); + event.end = helper->Ends()[t]; + event.demand_min = integer_trail->LowerBound(demands[t]); + events.push_back(event); } GeneratePrecedenceCuts("CumulativePrecedence", lp_values, std::move(events), capacity_max, model, manager); @@ -2590,12 +2586,15 @@ CutGenerator CreateNoOverlapPrecedenceCutGenerator( std::vector events; for (int t = 0; t < helper->NumTasks(); ++t) { if (!helper->IsPresent(t)) continue; - events.push_back({helper->StartMin(t), helper->StartMax(t), - helper->Starts()[t].LpValue(lp_values), - helper->Starts()[t], helper->EndMin(t), - helper->EndMax(t), - helper->Ends()[t].LpValue(lp_values), - helper->Ends()[t], IntegerValue(1)}); + PrecedenceEvent event; + event.start_min = helper->StartMin(t); + event.start_max = helper->StartMax(t); + event.start = helper->Starts()[t]; + event.end_min = helper->EndMin(t); + event.end_max = helper->EndMax(t); + event.end = helper->Ends()[t]; + event.demand_min = IntegerValue(1); + events.push_back(event); } GeneratePrecedenceCuts("NoOverlapPrecedence", lp_values, std::move(events), IntegerValue(1), model, @@ -2612,34 +2611,38 @@ CutGenerator CreateNoOverlapPrecedenceCutGenerator( // capacity_max. // For a no_overlap_2d constraint, y the other dimension of the box. struct CtEvent { - // The end of the x interval. - AffineExpression x_end; - // The start min of the x interval. IntegerValue x_start_min; // The size min of the x interval. IntegerValue x_size_min; + // The end of the x interval. + AffineExpression x_end; + // The lp value of the end of the x interval. double x_lp_end; // The start min of the y interval. IntegerValue y_start_min; + // The size min of the y interval. + IntegerValue y_size_min; + // The end max of the y interval. IntegerValue y_end_max; - // The min energy of the task. + // The min energy of the task (this is always larger or equal to x_size_min * + // y_size_min). IntegerValue energy_min; // Indicates if the events used the optional energy information from the // model. - bool use_energy; + bool use_energy = false; // Indicates if the cut is lifted, that is if it includes tasks that are not // strictly contained in the current time window. - bool lifted; + bool lifted = false; std::string DebugString() const { return absl::StrCat("CtEvent(x_end = ", x_end.DebugString(), @@ -2647,6 +2650,7 @@ struct CtEvent { ", x_size_min = ", x_size_min.value(), ", x_lp_end = ", x_lp_end, ", y_start_min = ", y_start_min.value(), + ", y_size_min = ", y_size_min.value(), ", y_end_max = ", y_end_max.value(), ", energy_min = ", energy_min.value(), ", use_energy = ", use_energy, ", lifted = ", lifted); @@ -2698,12 +2702,18 @@ void GenerateCompletionTimeCuts( sequence_start_min) { CtEvent event = events[before]; // Copy. event.lifted = true; - const IntegerValue old_x_size_min = event.x_size_min; - const IntegerValue new_x_size_min = + event.x_size_min = event.x_size_min + event.x_start_min - sequence_start_min; - event.x_size_min = new_x_size_min; event.x_start_min = sequence_start_min; - event.energy_min = event.energy_min * new_x_size_min / old_x_size_min; + // We cannot rescale the energy min correctly. + // Let's take the example of a box of size 2 * 20 that overlaps + // sequence start min by 1, and that can rotate by 90 degrees. + // The energy min is 40, size min is 2, size_max is 20. + // The lifted energy is 2 * 1, not 40 / 2. + // Therefore, we fallback to the x_size_min * y_size_min energy + // approximation. + event.energy_min = event.y_size_min * event.x_size_min; + event.use_energy = false; residual_tasks.push_back(event); } } @@ -2801,12 +2811,16 @@ CutGenerator CreateNoOverlapCompletionTimeCutGenerator( const IntegerValue size_min = helper->SizeMin(index); if (size_min > 0) { const AffineExpression end_expr = helper->Ends()[index]; - events.push_back({end_expr, helper->StartMin(index), size_min, - end_expr.LpValue(lp_values), - /*y_start_min=*/IntegerValue(0), - /*y_end_max=*/IntegerValue(1), - /*energy_min=*/size_min, /*use_energy=*/false, - /*lifted=*/false}); + CtEvent event; + event.x_start_min = helper->StartMin(index); + event.x_size_min = size_min; + event.x_end = end_expr; + event.x_lp_end = end_expr.LpValue(lp_values); + event.y_start_min = IntegerValue(0); + event.y_size_min = IntegerValue(1); + event.y_end_max = IntegerValue(1); + event.energy_min = size_min; + events.push_back(event); } } GenerateCompletionTimeCuts(cut_name, lp_values, std::move(events), @@ -2854,34 +2868,29 @@ CutGenerator CreateCumulativeCompletionTimeCutGenerator( if (helper->SizeMin(index) > 0 && integer_trail->LowerBound(demands[index]) > 0) { const AffineExpression end_expr = helper->Ends()[index]; - IntegerValue energy_min(0); - bool use_energy = false; - if (!energies.empty()) { - const LinearExpression& expr = energies[index]; - energy_min += expr.offset; - for (int e_index = 0; e_index < expr.vars.size(); ++e_index) { - const IntegerValue coeff = expr.coeffs[e_index]; - const IntegerVariable var = expr.vars[e_index]; - if (coeff > 0) { - energy_min += integer_trail->LowerBound(var) * coeff; - } else if (coeff < 0) { - energy_min += integer_trail->UpperBound(var) * coeff; - } - } - } + IntegerValue energy_min = + energies.empty() + ? IntegerValue(0) + : LinExprLowerBound(energies[index], *integer_trail); + const IntegerValue size_min = helper->SizeMin(index); const IntegerValue demand_min = integer_trail->LowerBound(demands[index]); + CtEvent event; + event.x_start_min = helper->StartMin(index); + event.x_size_min = size_min; + event.x_end = end_expr; + event.x_lp_end = end_expr.LpValue(lp_values); + event.y_start_min = IntegerValue(0); + event.y_size_min = demand_min; + event.y_end_max = IntegerValue(capacity_max); if (energy_min > size_min * demand_min) { - use_energy = true; + event.energy_min = energy_min; + event.use_energy = true; } else { - energy_min = size_min * demand_min; + event.energy_min = size_min * demand_min; } - events.push_back({end_expr, helper->StartMin(index), size_min, - end_expr.LpValue(lp_values), - /*y_start_min=*/IntegerValue(0), - /*y_end_max=*/capacity_max, energy_min, - use_energy, /*lifted=*/false}); + events.push_back(event); } } GenerateCompletionTimeCuts(cut_name, lp_values, std::move(events), @@ -2913,75 +2922,82 @@ CutGenerator CreateNoOverlap2dCompletionTimeCutGenerator( Trail* trail = model->GetOrCreate(); - result.generate_cuts = [trail, x_helper, y_helper, model]( - const absl::StrongVector& - lp_values, - LinearConstraintManager* manager) { - if (trail->CurrentDecisionLevel() > 0) return true; + result.generate_cuts = + [trail, x_helper, y_helper, model]( + const absl::StrongVector& lp_values, + LinearConstraintManager* manager) { + if (trail->CurrentDecisionLevel() > 0) return true; - if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false; - if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false; + if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false; + if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false; - const int num_boxes = x_helper->NumTasks(); - std::vector active_boxes; - std::vector cached_areas(num_boxes); - std::vector cached_rectangles(num_boxes); - for (int box = 0; box < num_boxes; ++box) { - cached_areas[box] = x_helper->SizeMin(box) * y_helper->SizeMin(box); - if (cached_areas[box] == 0) continue; - if (!y_helper->IsPresent(box) || !y_helper->IsPresent(box)) continue; + const int num_boxes = x_helper->NumTasks(); + std::vector active_boxes; + std::vector cached_areas(num_boxes); + std::vector cached_rectangles(num_boxes); + for (int box = 0; box < num_boxes; ++box) { + cached_areas[box] = x_helper->SizeMin(box) * y_helper->SizeMin(box); + if (cached_areas[box] == 0) continue; + if (!y_helper->IsPresent(box) || !y_helper->IsPresent(box)) continue; - // TODO(user): It might be possible/better to use some shifted value - // here, but for now this code is not in the hot spot, so better be - // defensive and only do connected components on really disjoint - // boxes. - Rectangle& rectangle = cached_rectangles[box]; - rectangle.x_min = x_helper->StartMin(box); - rectangle.x_max = x_helper->EndMax(box); - rectangle.y_min = y_helper->StartMin(box); - rectangle.y_max = y_helper->EndMax(box); + // TODO(user): It might be possible/better to use some shifted value + // here, but for now this code is not in the hot spot, so better be + // defensive and only do connected components on really disjoint + // boxes. + Rectangle& rectangle = cached_rectangles[box]; + rectangle.x_min = x_helper->StartMin(box); + rectangle.x_max = x_helper->EndMax(box); + rectangle.y_min = y_helper->StartMin(box); + rectangle.y_max = y_helper->EndMax(box); - active_boxes.push_back(box); - } - - if (active_boxes.size() <= 1) return true; - - std::vector> components = GetOverlappingRectangleComponents( - cached_rectangles, absl::MakeSpan(active_boxes)); - for (absl::Span boxes : components) { - if (boxes.size() <= 1) continue; - - auto generate_cuts = [&lp_values, model, manager, &boxes, &cached_areas]( - const std::string& cut_name, - SchedulingConstraintHelper* x_helper, - SchedulingConstraintHelper* y_helper) { - std::vector events; - - for (const int box : boxes) { - const AffineExpression x_end_expr = x_helper->Ends()[box]; - events.push_back( - {x_end_expr, x_helper->ShiftedStartMin(box), - x_helper->SizeMin(box), x_end_expr.LpValue(lp_values), - y_helper->ShiftedStartMin(box), y_helper->ShiftedEndMax(box), - /*energy_min=*/x_helper->SizeMin(box) * y_helper->SizeMin(box), - /*use_energy=*/false, /*lifted=*/false}); + active_boxes.push_back(box); } - GenerateCompletionTimeCuts(cut_name, lp_values, std::move(events), - /*use_lifting=*/true, model, manager); - }; + if (active_boxes.size() <= 1) return true; - if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false; - if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false; - generate_cuts("NoOverlap2dXCompletionTime", x_helper, y_helper); - generate_cuts("NoOverlap2dYCompletionTime", y_helper, x_helper); - if (!x_helper->SynchronizeAndSetTimeDirection(false)) return false; - if (!y_helper->SynchronizeAndSetTimeDirection(false)) return false; - generate_cuts("NoOverlap2dXCompletionTimeMirror", x_helper, y_helper); - generate_cuts("NoOverlap2dYCompletionTimeMirror", y_helper, x_helper); - } - return true; - }; + std::vector> components = + GetOverlappingRectangleComponents(cached_rectangles, + absl::MakeSpan(active_boxes)); + for (absl::Span boxes : components) { + if (boxes.size() <= 1) continue; + + auto generate_cuts = [&lp_values, model, manager, &boxes, + &cached_areas]( + const std::string& cut_name, + SchedulingConstraintHelper* x_helper, + SchedulingConstraintHelper* y_helper) { + std::vector events; + + for (const int box : boxes) { + const AffineExpression x_end_expr = x_helper->Ends()[box]; + CtEvent event; + event.x_start_min = x_helper->ShiftedStartMin(box); + event.x_size_min = x_helper->SizeMin(box); + event.x_end = x_end_expr; + event.x_lp_end = x_end_expr.LpValue(lp_values); + event.y_start_min = y_helper->ShiftedStartMin(box); + event.y_size_min = y_helper->SizeMin(box); + event.y_end_max = y_helper->ShiftedEndMax(box); + event.energy_min = + x_helper->SizeMin(box) * y_helper->SizeMin(box); + events.push_back(event); + } + + GenerateCompletionTimeCuts(cut_name, lp_values, std::move(events), + /*use_lifting=*/true, model, manager); + }; + + if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false; + if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false; + generate_cuts("NoOverlap2dXCompletionTime", x_helper, y_helper); + generate_cuts("NoOverlap2dYCompletionTime", y_helper, x_helper); + if (!x_helper->SynchronizeAndSetTimeDirection(false)) return false; + if (!y_helper->SynchronizeAndSetTimeDirection(false)) return false; + generate_cuts("NoOverlap2dXCompletionTimeMirror", x_helper, y_helper); + generate_cuts("NoOverlap2dYCompletionTimeMirror", y_helper, x_helper); + } + return true; + }; return result; } diff --git a/ortools/sat/linear_constraint.cc b/ortools/sat/linear_constraint.cc index c609af03b1..f6602b12b0 100644 --- a/ortools/sat/linear_constraint.cc +++ b/ortools/sat/linear_constraint.cc @@ -47,6 +47,20 @@ void LinearConstraintBuilder::AddTerm(AffineExpression expr, if (ub_ < kMaxIntegerValue) ub_ -= coeff * expr.constant; } +void LinearConstraintBuilder::AddLinearExpression( + const LinearExpression& expr) { + for (int i = 0; i < expr.vars.size(); ++i) { + // We must use positive variables. + if (VariableIsPositive(expr.vars[i])) { + terms_.push_back({expr.vars[i], expr.coeffs[i]}); + } else { + terms_.push_back({NegationOf(expr.vars[i]), -expr.coeffs[i]}); + } + } + if (lb_ > kMinIntegerValue) lb_ -= expr.offset; + if (ub_ < kMaxIntegerValue) ub_ -= expr.offset; +} + void LinearConstraintBuilder::AddConstant(IntegerValue value) { if (lb_ > kMinIntegerValue) lb_ -= value; if (ub_ < kMaxIntegerValue) ub_ -= value; @@ -237,6 +251,15 @@ void MakeAllVariablesPositive(LinearConstraint* constraint) { } } +double LinearExpression::LpValue( + const absl::StrongVector& lp_values) const { + double result = ToDouble(offset); + for (int i = 0; i < vars.size(); ++i) { + result += ToDouble(coeffs[i]) * lp_values[vars[i]]; + } + return result; +} + // TODO(user): it would be better if LinearConstraint natively supported // term and not two separated vectors. Fix? // diff --git a/ortools/sat/linear_constraint.h b/ortools/sat/linear_constraint.h index 72c69b6a50..9f4cd6cbb2 100644 --- a/ortools/sat/linear_constraint.h +++ b/ortools/sat/linear_constraint.h @@ -82,6 +82,48 @@ struct LinearConstraint { } }; +// Helper struct to model linear expression for lin_min/lin_max constraints. The +// canonical expression should only contain positive coefficients. +struct LinearExpression { + std::vector vars; + std::vector coeffs; + IntegerValue offset = IntegerValue(0); + // Return the evaluation of the linear expression using the values from + // lp_values. + double LpValue( + const absl::StrongVector& lp_values) const; +}; + +// Returns the same expression in the canonical form (all positive +// coefficients). +LinearExpression CanonicalizeExpr(const LinearExpression& expr); + +// Returns lower bound of linear expression using variable bounds of the +// variables in expression. Assumes Canonical expression (all positive +// coefficients). +IntegerValue LinExprLowerBound(const LinearExpression& expr, + const IntegerTrail& integer_trail); + +// Returns upper bound of linear expression using variable bounds of the +// variables in expression. Assumes Canonical expression (all positive +// coefficients). +IntegerValue LinExprUpperBound(const LinearExpression& expr, + const IntegerTrail& integer_trail); + +// Preserves canonicality. +LinearExpression NegationOf(const LinearExpression& expr); + +// Returns the same expression with positive variables. +LinearExpression PositiveVarExpr(const LinearExpression& expr); + +// Returns the coefficient of the variable in the expression. Works in linear +// time. +// Note: GetCoefficient(NegationOf(var, expr)) == -GetCoefficient(var, expr). +IntegerValue GetCoefficient(const IntegerVariable var, + const LinearExpression& expr); +IntegerValue GetCoefficientOfPositiveVar(const IntegerVariable var, + const LinearExpression& expr); + // Allow to build a LinearConstraint while making sure there is no duplicate // variables. Note that we do not simplify literal/variable that are currently // fixed here. @@ -97,6 +139,7 @@ class LinearConstraintBuilder { // Adds var * coeff to the constraint. void AddTerm(IntegerVariable var, IntegerValue coeff); void AddTerm(AffineExpression expr, IntegerValue coeff); + void AddLinearExpression(const LinearExpression& expr); // Add value as a constant term to the linear equation. void AddConstant(IntegerValue value); @@ -169,44 +212,6 @@ void CanonicalizeConstraint(LinearConstraint* ct); // Returns false if duplicate variables are found in ct. bool NoDuplicateVariable(const LinearConstraint& ct); -// Helper struct to model linear expression for lin_min/lin_max constraints. The -// canonical expression should only contain positive coefficients. -struct LinearExpression { - std::vector vars; - std::vector coeffs; - IntegerValue offset = IntegerValue(0); -}; - -// Returns the same expression in the canonical form (all positive -// coefficients). -LinearExpression CanonicalizeExpr(const LinearExpression& expr); - -// Returns lower bound of linear expression using variable bounds of the -// variables in expression. Assumes Canonical expression (all positive -// coefficients). -IntegerValue LinExprLowerBound(const LinearExpression& expr, - const IntegerTrail& integer_trail); - -// Returns upper bound of linear expression using variable bounds of the -// variables in expression. Assumes Canonical expression (all positive -// coefficients). -IntegerValue LinExprUpperBound(const LinearExpression& expr, - const IntegerTrail& integer_trail); - -// Preserves canonicality. -LinearExpression NegationOf(const LinearExpression& expr); - -// Returns the same expression with positive variables. -LinearExpression PositiveVarExpr(const LinearExpression& expr); - -// Returns the coefficient of the variable in the expression. Works in linear -// time. -// Note: GetCoefficient(NegationOf(var, expr)) == -GetCoefficient(var, expr). -IntegerValue GetCoefficient(const IntegerVariable var, - const LinearExpression& expr); -IntegerValue GetCoefficientOfPositiveVar(const IntegerVariable var, - const LinearExpression& expr); - } // namespace sat } // namespace operations_research diff --git a/ortools/sat/linear_relaxation.cc b/ortools/sat/linear_relaxation.cc index 130769a50e..17867cadd2 100644 --- a/ortools/sat/linear_relaxation.cc +++ b/ortools/sat/linear_relaxation.cc @@ -699,11 +699,7 @@ void AddCumulativeRelaxation(const std::vector& intervals, } else if (!energies.empty()) { // We prefer the energy additional info instead of the McCormick // relaxation. - const LinearExpression& energy = energies[i]; - lc.AddConstant(energy.offset); - for (int j = 0; j < energy.vars.size(); ++j) { - lc.AddTerm(energy.vars[j], energy.coeffs[j]); - } + lc.AddLinearExpression(energies[i]); } else { // demand and size are not fixed. DCHECK(!demands.empty()); // We use McCormick equation. @@ -745,8 +741,7 @@ void AppendCumulativeRelaxation(const CpModelProto& model_proto, for (int i = 0; i < ct.cumulative().energies_size(); ++i) { // Note: Cut generator requires all expressions to contain only positive // vars. - energies.push_back(PositiveVarExpr( - mapping->GetExprFromProto(ct.cumulative().energies(i)))); + energies.push_back(mapping->GetExprFromProto(ct.cumulative().energies(i))); } AddCumulativeRelaxation(intervals, demands, energies, capacity_upper_bound, @@ -1131,8 +1126,7 @@ void AddCumulativeCutGenerator(const ConstraintProto& ct, Model* m, for (int i = 0; i < ct.cumulative().energies_size(); ++i) { // Note: Cut generator requires all expressions to contain only positive // vars. - energies.push_back(PositiveVarExpr( - mapping->GetExprFromProto(ct.cumulative().energies(i)))); + energies.push_back(mapping->GetExprFromProto(ct.cumulative().energies(i))); } relaxation->cut_generators.push_back( @@ -1170,7 +1164,8 @@ void AddNoOverlap2dCutGenerator(const ConstraintProto& ct, Model* m, mapping->Intervals(ct.no_overlap_2d().x_intervals()); std::vector y_intervals = mapping->Intervals(ct.no_overlap_2d().y_intervals()); - // TODO(user): Add energy cuts if intervals have variable sizes. + // TODO(user): We can add CumulativeEnergyCuts for no_overlap_2d if boxes + // do not have a fixed size. relaxation->cut_generators.push_back( CreateNoOverlap2dCompletionTimeCutGenerator(x_intervals, y_intervals, m)); }