[CP-SAT] fix cuts; simplify LinearConstraintBuilder::AddLinearExpression signature

This commit is contained in:
Laurent Perron
2021-06-30 16:26:59 +02:00
parent d4142027c8
commit 7485b31723
4 changed files with 219 additions and 180 deletions

View File

@@ -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<PrecedenceEvent> 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<PrecedenceEvent> 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<Trail>();
result.generate_cuts = [trail, x_helper, y_helper, model](
const absl::StrongVector<IntegerVariable, double>&
lp_values,
LinearConstraintManager* manager) {
if (trail->CurrentDecisionLevel() > 0) return true;
result.generate_cuts =
[trail, x_helper, y_helper, model](
const absl::StrongVector<IntegerVariable, double>& 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<int> active_boxes;
std::vector<IntegerValue> cached_areas(num_boxes);
std::vector<Rectangle> 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<int> active_boxes;
std::vector<IntegerValue> cached_areas(num_boxes);
std::vector<Rectangle> 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<absl::Span<int>> components = GetOverlappingRectangleComponents(
cached_rectangles, absl::MakeSpan(active_boxes));
for (absl::Span<int> 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<CtEvent> 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<absl::Span<int>> components =
GetOverlappingRectangleComponents(cached_rectangles,
absl::MakeSpan(active_boxes));
for (absl::Span<int> 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<CtEvent> 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;
}

View File

@@ -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<IntegerVariable, double>& 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?
//

View File

@@ -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<IntegerVariable> vars;
std::vector<IntegerValue> coeffs;
IntegerValue offset = IntegerValue(0);
// Return the evaluation of the linear expression using the values from
// lp_values.
double LpValue(
const absl::StrongVector<IntegerVariable, double>& 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<IntegerVariable> vars;
std::vector<IntegerValue> 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

View File

@@ -699,11 +699,7 @@ void AddCumulativeRelaxation(const std::vector<IntervalVariable>& 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<IntervalVariable> 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));
}