28 #include "absl/log/check.h"
29 #include "absl/strings/str_cat.h"
30 #include "absl/strings/str_join.h"
31 #include "absl/types/span.h"
54 const double kMinCutViolation = 1e-4;
56 void AddIntegerVariableFromIntervals(SchedulingConstraintHelper* helper,
58 std::vector<IntegerVariable>* vars) {
59 IntegerEncoder* encoder =
model->GetOrCreate<IntegerEncoder>();
60 for (
int t = 0; t < helper->NumTasks(); ++t) {
62 vars->push_back(helper->Starts()[t].var);
65 vars->push_back(helper->Sizes()[t].var);
68 vars->push_back(helper->Ends()[t].var);
70 if (helper->IsOptional(t) && !helper->IsAbsent(t) &&
71 !helper->IsPresent(t)) {
72 const Literal l = helper->PresenceLiteral(t);
74 if (!encoder->LiteralOrNegationHasView(l, &view)) {
77 vars->push_back(view);
85 : x_start_min(x_helper->StartMin(t)),
86 x_start_max(x_helper->StartMax(t)),
87 x_end_min(x_helper->EndMin(t)),
88 x_end_max(x_helper->EndMax(t)),
89 x_size_min(x_helper->SizeMin(t)) {}
173 ABSL_MUST_USE_RESULT
bool AddOneEvent(
174 const EnergyEvent& event, IntegerValue window_start,
175 IntegerValue window_end, LinearConstraintBuilder* cut,
176 bool* add_energy_to_name =
nullptr,
bool* add_quadratic_to_name =
nullptr,
177 bool* add_opt_to_name =
nullptr,
bool* add_lifted_to_name =
nullptr) {
178 DCHECK(cut !=
nullptr);
180 if (event.x_end_min <= window_start || event.x_start_max >= window_end) {
184 if (event.x_start_min >= window_start && event.x_end_max <= window_end) {
186 cut->AddLinearExpression(event.linearized_energy);
188 if (event.energy_is_quadratic && add_quadratic_to_name !=
nullptr) {
189 *add_quadratic_to_name =
true;
191 if (add_energy_to_name !=
nullptr &&
192 event.energy_min > event.x_size_min * event.y_size_min) {
193 *add_energy_to_name =
true;
195 if (!event.IsPresent() && add_opt_to_name !=
nullptr) {
196 *add_opt_to_name =
true;
199 const IntegerValue min_overlap =
200 event.GetMinOverlap(window_start, window_end);
201 if (min_overlap <= 0)
return true;
202 if (add_lifted_to_name !=
nullptr) *add_lifted_to_name =
true;
204 if (event.IsPresent()) {
205 const std::vector<LiteralValueValue>&
energy =
event.decomposed_energy;
207 cut->AddTerm(event.y_size, min_overlap);
209 const IntegerValue window_size = window_end - window_start;
210 for (
const auto [lit, fixed_size, fixed_demand] :
energy) {
211 const IntegerValue alt_end_min =
212 std::max(event.x_end_min, event.x_start_min + fixed_size);
213 const IntegerValue alt_start_max =
214 std::min(event.x_start_max, event.x_end_max - fixed_size);
215 const IntegerValue energy_min =
217 std::min({alt_end_min - window_start, window_end - alt_start_max,
218 fixed_size, window_size});
219 if (energy_min == 0)
continue;
220 if (!cut->AddLiteralTerm(lit, energy_min))
return false;
222 if (add_energy_to_name !=
nullptr) *add_energy_to_name =
true;
225 if (add_opt_to_name !=
nullptr) *add_opt_to_name =
true;
227 event.x_start_min, event.x_start_max, event.x_end_min,
228 event.x_end_max, event.x_size_min, event.y_size_min,
229 event.decomposed_energy, window_start, window_end);
230 if (min_energy > event.x_size_min * event.y_size_min &&
231 add_energy_to_name !=
nullptr) {
232 *add_energy_to_name =
true;
234 if (!cut->AddLiteralTerm(Literal(event.presence_literal_index),
245 std::vector<int64_t> FindPossibleDemands(
const EnergyEvent& event,
246 const VariablesAssignment& assignment,
247 IntegerTrail* integer_trail) {
248 std::vector<int64_t> possible_demands;
249 if (event.decomposed_energy.empty()) {
250 if (integer_trail->IsFixed(event.y_size)) {
251 possible_demands.push_back(
252 integer_trail->FixedValue(event.y_size).value());
254 if (integer_trail->InitialVariableDomain(event.y_size.var).Size() >
258 for (
const int64_t var_value :
259 integer_trail->InitialVariableDomain(event.y_size.var).Values()) {
260 possible_demands.push_back(event.y_size.ValueAt(var_value).value());
264 for (
const auto [lit, fixed_size, fixed_demand] : event.decomposed_energy) {
265 if (assignment.LiteralIsFalse(lit))
continue;
266 possible_demands.push_back(fixed_demand.value());
269 return possible_demands;
275 const std::vector<EnergyEvent>& events, IntegerValue window_start,
276 IntegerValue window_end,
double available_energy_lp,
280 double energy_from_events_lp = 0.0;
281 LinearConstraintBuilder tmp_energy(
model);
282 for (
const EnergyEvent& event : events) {
284 if (!AddOneEvent(event, window_start, window_end, &tmp_energy)) {
287 energy_from_events_lp += tmp_energy.BuildExpression().LpValue(lp_values);
290 return energy_from_events_lp >=
291 available_energy_lp * (1.0 + kMinCutViolation);
309 const std::string& cut_name,
311 std::vector<EnergyEvent> events, IntegerValue
capacity,
328 struct OverloadedTimeWindowWithMakespan {
331 IntegerValue fixed_energy_rhs;
332 bool use_makespan =
false;
333 bool use_subset_sum =
false;
336 std::vector<OverloadedTimeWindowWithMakespan> overloaded_time_windows;
341 std::vector<IntegerValue> time_points;
342 std::vector<std::vector<int64_t>> possible_demands(events.size());
343 const IntegerValue makespan_min = integer_trail->
LowerBound(makespan);
346 for (
int i = 0; i < events.size(); ++i) {
348 if (event.x_start_min < makespan_min) {
349 time_points.push_back(event.x_start_min);
351 if (event.x_start_max < makespan_min) {
352 time_points.push_back(event.x_start_max);
354 if (event.x_end_min < makespan_min) {
355 time_points.push_back(event.x_end_min);
357 if (event.x_end_max < makespan_min) {
358 time_points.push_back(event.x_end_max);
360 max_end_min =
std::max(max_end_min, event.x_end_min);
361 max_end_max =
std::max(max_end_max, event.x_end_max);
362 possible_demands[i] = FindPossibleDemands(event, assignment, integer_trail);
364 time_points.push_back(makespan_min);
365 time_points.push_back(max_end_max);
368 const int num_time_points = time_points.size();
369 absl::flat_hash_map<IntegerValue, IntegerValue> reachable_capacity_ending_at;
372 for (
int i = 1; i < num_time_points; ++i) {
373 const IntegerValue window_start = time_points[i - 1];
374 const IntegerValue window_end = time_points[i];
376 for (
int i = 0; i < events.size(); ++i) {
378 if (event.x_start_min >= window_end || event.x_end_max <= window_start) {
381 if (possible_demands[i].empty()) {
383 reachable_capacity_subset_sum.
Add(
capacity.value());
385 reachable_capacity_subset_sum.
AddChoices(possible_demands[i]);
389 reachable_capacity_ending_at[window_end] =
394 const double makespan_lp = makespan.
LpValue(lp_values);
395 const double makespan_min_lp =
ToDouble(makespan_min);
396 for (
int i = 0; i + 1 < num_time_points; ++i) {
400 const IntegerValue window_start = time_points[i];
402 if (window_start >= max_end_min)
break;
404 IntegerValue cumulated_max_energy = 0;
405 IntegerValue cumulated_max_energy_before_makespan_min = 0;
406 bool use_subset_sum =
false;
407 bool use_subset_sum_before_makespan_min =
false;
409 for (
int j = i + 1; j < num_time_points; ++j) {
410 const IntegerValue strip_start = time_points[j - 1];
411 const IntegerValue window_end = time_points[j];
412 const IntegerValue max_reachable_capacity_in_current_strip =
413 reachable_capacity_ending_at[window_end];
414 DCHECK_LE(max_reachable_capacity_in_current_strip,
capacity);
417 if (max_reachable_capacity_in_current_strip <
capacity) {
418 use_subset_sum =
true;
419 if (window_end <= makespan_min) {
420 use_subset_sum_before_makespan_min =
true;
424 const IntegerValue energy_in_strip =
425 (window_end - strip_start) * max_reachable_capacity_in_current_strip;
426 cumulated_max_energy += energy_in_strip;
427 if (window_end <= makespan_min) {
428 cumulated_max_energy_before_makespan_min += energy_in_strip;
431 if (window_start >= makespan_min) {
432 DCHECK_EQ(cumulated_max_energy_before_makespan_min, 0);
434 DCHECK_LE(cumulated_max_energy,
capacity * (window_end - window_start));
435 const double max_energy_up_to_makespan_lp =
436 strip_start >= makespan_min
437 ?
ToDouble(cumulated_max_energy_before_makespan_min) +
438 (makespan_lp - makespan_min_lp) * capacity_lp
439 : std::numeric_limits<double>::infinity();
446 const bool use_makespan =
447 max_energy_up_to_makespan_lp <=
448 ToDouble(cumulated_max_energy) + kMinCutViolation;
449 const double available_energy_lp = use_makespan
450 ? max_energy_up_to_makespan_lp
452 if (CutIsEfficient(events, window_start, window_end, available_energy_lp,
454 OverloadedTimeWindowWithMakespan w;
455 w.start = window_start;
457 w.fixed_energy_rhs = use_makespan
458 ? cumulated_max_energy_before_makespan_min
459 : cumulated_max_energy;
460 w.use_makespan = use_makespan;
462 use_makespan ? use_subset_sum_before_makespan_min : use_subset_sum;
463 overloaded_time_windows.push_back(std::move(w));
468 if (overloaded_time_windows.empty())
return;
470 VLOG(2) <<
"GenerateCumulativeEnergeticCutsWithMakespanAndFixedCapacity: "
471 << events.size() <<
" events, " << time_points.size()
472 <<
" time points, " << overloaded_time_windows.size()
473 <<
" overloads detected";
476 for (
const auto& w : overloaded_time_windows) {
477 bool cut_generated =
true;
478 bool add_opt_to_name =
false;
479 bool add_lifted_to_name =
false;
480 bool add_quadratic_to_name =
false;
481 bool add_energy_to_name =
false;
484 if (w.use_makespan) {
491 if (!AddOneEvent(event, w.start, w.end, &cut, &add_energy_to_name,
492 &add_quadratic_to_name, &add_opt_to_name,
493 &add_lifted_to_name)) {
494 cut_generated =
false;
500 std::string full_name = cut_name;
501 if (add_opt_to_name) full_name.append(
"_optional");
502 if (add_quadratic_to_name) full_name.append(
"_quadratic");
503 if (add_lifted_to_name) full_name.append(
"_lifted");
504 if (add_energy_to_name) full_name.append(
"_energy");
505 if (w.use_makespan) full_name.append(
"_makespan");
506 if (w.use_subset_sum) full_name.append(
"_subsetsum");
507 top_n_cuts.
AddCut(cut.
Build(), full_name, lp_values);
515 const std::string& cut_name,
519 double max_possible_energy_lp = 0.0;
521 max_possible_energy_lp +=
event.linearized_energy_lp_value;
531 struct OverloadedTimeWindow {
535 std::vector<OverloadedTimeWindow> overloaded_time_windows;
536 const double capacity_lp =
capacity.LpValue(lp_values);
540 absl::btree_set<IntegerValue> time_points_set;
543 time_points_set.insert(event.x_start_min);
544 time_points_set.insert(event.x_start_max);
545 time_points_set.insert(event.x_end_min);
546 time_points_set.insert(event.x_end_max);
547 max_end_min =
std::max(max_end_min, event.x_end_min);
549 const std::vector<IntegerValue> time_points(time_points_set.begin(),
550 time_points_set.end());
551 const int num_time_points = time_points.size();
553 for (
int i = 0; i + 1 < num_time_points; ++i) {
557 const IntegerValue window_start = time_points[i];
559 if (window_start >= max_end_min)
break;
561 for (
int j = i + 1; j < num_time_points; ++j) {
562 const IntegerValue window_end = time_points[j];
563 const double available_energy_lp =
564 ToDouble(window_end - window_start) * capacity_lp;
565 if (available_energy_lp >= max_possible_energy_lp)
break;
566 if (CutIsEfficient(events, window_start, window_end, available_energy_lp,
568 overloaded_time_windows.push_back({window_start, window_end});
573 if (overloaded_time_windows.empty())
return;
575 VLOG(2) <<
"GenerateCumulativeEnergeticCuts: " << events.size() <<
" events, "
576 << time_points.size() <<
" time points, "
577 << overloaded_time_windows.size() <<
" overloads detected";
580 for (
const auto& [window_start, window_end] : overloaded_time_windows) {
581 bool cut_generated =
true;
582 bool add_opt_to_name =
false;
583 bool add_lifted_to_name =
false;
584 bool add_quadratic_to_name =
false;
585 bool add_energy_to_name =
false;
593 if (!AddOneEvent(event, window_start, window_end, &cut,
594 &add_energy_to_name, &add_quadratic_to_name,
595 &add_opt_to_name, &add_lifted_to_name)) {
596 cut_generated =
false;
602 std::string full_name = cut_name;
603 if (add_opt_to_name) full_name.append(
"_optional");
604 if (add_quadratic_to_name) full_name.append(
"_quadratic");
605 if (add_lifted_to_name) full_name.append(
"_lifted");
606 if (add_energy_to_name) full_name.append(
"_energy");
607 top_n_cuts.
AddCut(cut.
Build(), full_name, lp_values);
617 std::vector<IntegerVariable>* vars) {
620 if (!integer_trail->IsFixed(demand_expr)) {
621 vars->push_back(demand_expr.var);
626 for (
const auto& lit_val_val : product) {
631 vars->push_back(view);
635 if (!integer_trail->IsFixed(
capacity)) {
643 const std::optional<AffineExpression>& makespan,
Model*
model) {
647 AddIntegerVariableFromIntervals(helper,
model, &result.
vars);
659 std::vector<EnergyEvent> events;
660 for (
int i = 0; i < helper->
NumTasks(); ++i) {
683 "CumulativeEnergyM", lp_values, events,
699 const std::optional<AffineExpression>& makespan,
Model*
model) {
702 AddIntegerVariableFromIntervals(helper,
model, &result.
vars);
712 std::vector<EnergyEvent> events;
713 for (
int i = 0; i < helper->
NumTasks(); ++i) {
715 if (helper->
SizeMin(i) == 0)
continue;
718 e.
y_size = IntegerValue(1);
729 if (makespan.has_value()) {
731 "NoOverlapEnergyM", lp_values, events,
745 const std::vector<std::vector<LiteralValueValue>>& energies,
746 absl::Span<int> rectangles,
const std::string& cut_name,
751 std::vector<EnergyEvent> events;
752 for (
const int rect : rectangles) {
753 if (y_helper->
SizeMax(rect) == 0 || x_helper->
SizeMax(rect) == 0) {
777 if (events.empty())
return;
780 double average_d = 0.0;
781 for (
const auto& e : events) {
782 average_d +=
ToDouble(e.y_min + e.y_max);
784 const double average = average_d / 2.0 /
static_cast<double>(events.size());
785 for (
auto& e : events) {
792 std::sort(events.begin(), events.end(),
794 return std::tie(a.x_start_min, a.y_spread, a.x_end_max) <
795 std::tie(b.x_start_min, b.y_spread, b.x_end_max);
799 double sum_of_all_energies = 0.0;
800 for (
const auto& e : events) {
801 sum_of_all_energies += e.linearized_energy_lp_value;
805 for (
int i1 = 0; i1 + 1 < events.size(); ++i1) {
808 int max_violation_end_index = -1;
809 double max_relative_violation = 1.0 + kMinCutViolation;
810 IntegerValue max_violation_window_start(0);
811 IntegerValue max_violation_window_end(0);
812 IntegerValue max_violation_y_min(0);
813 IntegerValue max_violation_y_max(0);
814 IntegerValue max_violation_area(0);
815 bool max_violation_use_precise_area =
false;
818 double energy_lp = 0.0;
823 capacity_profile.
Clear();
827 std::vector<EnergyEvent> residual_events(events.begin() + i1, events.end());
828 std::sort(residual_events.begin(), residual_events.end(),
830 return std::tie(a.x_end_max, a.y_spread) <
831 std::tie(b.x_end_max, b.y_spread);
835 for (
int i2 = 0; i2 < residual_events.size(); ++i2) {
848 if (i2 + 1 < residual_events.size() &&
849 residual_events[i2 + 1].x_start_min >= window_min &&
850 residual_events[i2 + 1].x_end_max <= window_max &&
851 residual_events[i2 + 1].y_min >= y_min &&
852 residual_events[i2 + 1].y_max <= y_max) {
860 bool use_precise_area =
false;
861 IntegerValue precise_area(0);
862 double area_lp = 0.0;
863 const IntegerValue bbox_area =
864 (window_max - window_min) * (y_max - y_min);
866 use_precise_area = precise_area < bbox_area;
869 if (area_lp >= sum_of_all_energies) {
874 const double relative_violation = energy_lp / area_lp;
875 if (relative_violation > max_relative_violation) {
876 max_violation_end_index = i2;
877 max_relative_violation = relative_violation;
878 max_violation_window_start = window_min;
879 max_violation_window_end = window_max;
880 max_violation_y_min = y_min;
881 max_violation_y_max = y_max;
882 max_violation_area =
std::min(precise_area, bbox_area);
883 max_violation_use_precise_area = use_precise_area;
887 if (max_violation_end_index == -1)
continue;
891 bool add_opt_to_name =
false;
892 bool add_quadratic_to_name =
false;
893 bool add_energy_to_name =
false;
895 for (
int i2 = 0; i2 <= max_violation_end_index; ++i2) {
898 if (!event.IsPresent()) add_opt_to_name =
true;
899 if (event.energy_is_quadratic) add_quadratic_to_name =
true;
900 if (event.energy_min > event.x_size_min * event.y_size_min) {
901 add_energy_to_name =
true;
904 std::string full_name = cut_name;
905 if (add_opt_to_name) full_name.append(
"_optional");
906 if (add_quadratic_to_name) full_name.append(
"_quadratic");
907 if (add_energy_to_name) full_name.append(
"_energy");
908 if (max_violation_use_precise_area) full_name.append(
"_precise");
909 top_n_cuts.
AddCut(cut.
Build(), full_name, lp_values);
915 const std::vector<IntervalVariable>& x_intervals,
916 const std::vector<IntervalVariable>& y_intervals,
Model*
model) {
924 AddIntegerVariableFromIntervals(x_helper,
model, &result.
vars);
925 AddIntegerVariableFromIntervals(y_helper,
model, &result.
vars);
930 model->TakeOwnership(x_demands_helper);
933 model->TakeOwnership(y_demands_helper);
935 std::vector<std::vector<LiteralValueValue>> energies;
936 const int num_rectangles = x_intervals.size();
937 for (
int i = 0; i < num_rectangles; ++i) {
943 [x_helper, y_helper, x_demands_helper, y_demands_helper,
model, energies](
951 const int num_rectangles = x_helper->
NumTasks();
952 std::vector<int> active_rectangles;
953 std::vector<Rectangle> cached_rectangles(num_rectangles);
954 for (
int rect = 0; rect < num_rectangles; ++rect) {
968 Rectangle& rectangle = cached_rectangles[rect];
969 rectangle.x_min = x_helper->
StartMin(rect);
970 rectangle.x_max = x_helper->
EndMax(rect);
971 rectangle.y_min = y_helper->
StartMin(rect);
972 rectangle.y_max = y_helper->
EndMax(rect);
974 active_rectangles.push_back(rect);
977 if (active_rectangles.size() <= 1)
return true;
979 std::vector<absl::Span<int>> components =
981 cached_rectangles, absl::MakeSpan(active_rectangles));
984 for (absl::Span<int> rectangles : components) {
985 if (rectangles.size() <= 1)
continue;
988 energies, rectangles,
"NoOverlap2dXEnergy", lp_values,
model,
989 manager, x_helper, y_helper, y_demands_helper);
991 energies, rectangles,
"NoOverlap2dYEnergy", lp_values,
model,
992 manager, y_helper, x_helper, x_demands_helper);
1006 AddIntegerVariableFromIntervals(helper,
model, &result.
vars);
1009 struct TimeTableEvent {
1013 double demand_lp = 0.0;
1014 bool is_positive =
false;
1015 bool use_energy =
false;
1016 bool is_optional =
false;
1028 std::vector<TimeTableEvent> events;
1029 const double capacity_lp =
capacity.LpValue(lp_values);
1033 for (
int i = 0; i < helper->
NumTasks(); ++i) {
1042 e1.interval_index = i;
1050 e1.demand_lp = e1.demand.LpValue(lp_values);
1051 e1.is_positive =
true;
1055 TimeTableEvent e2 = e1;
1057 e2.is_positive =
false;
1059 events.push_back(e1);
1060 events.push_back(e2);
1066 std::sort(events.begin(), events.end(),
1067 [](
const TimeTableEvent& i,
const TimeTableEvent& j) {
1068 if (i.time == j.time) {
1069 if (i.is_positive == j.is_positive) {
1070 return i.interval_index < j.interval_index;
1072 return !i.is_positive;
1074 return i.time < j.time;
1077 double sum_of_demand_lp = 0.0;
1078 bool positive_event_added_since_last_check =
false;
1079 for (
int i = 0; i < events.size(); ++i) {
1080 const TimeTableEvent& e = events[i];
1081 if (e.is_positive) {
1082 positive_event_added_since_last_check =
true;
1083 sum_of_demand_lp += e.demand_lp;
1087 if (positive_event_added_since_last_check) {
1090 positive_event_added_since_last_check =
false;
1092 if (sum_of_demand_lp >= capacity_lp + kMinCutViolation) {
1094 bool use_energy =
false;
1095 bool use_optional =
false;
1101 DCHECK(!events[i].is_positive);
1102 const IntegerValue time_point = events[i - 1].time;
1104 for (
int j = 0; j < i; ++j) {
1105 const TimeTableEvent& cut_event = events[j];
1106 const int t = cut_event.interval_index;
1107 DCHECK_LE(helper->
StartMax(t), time_point);
1108 if (!cut_event.is_positive || helper->
EndMin(t) <= time_point) {
1113 use_energy |= cut_event.use_energy;
1114 use_optional |= cut_event.is_optional;
1117 std::string cut_name =
"CumulativeTimeTable";
1118 if (use_optional) cut_name +=
"_optional";
1119 if (use_energy) cut_name +=
"_energy";
1120 top_n_cuts.AddCut(cut.
Build(), cut_name, lp_values);
1126 sum_of_demand_lp -= e.demand_lp;
1128 top_n_cuts.TransferToManager(lp_values, manager);
1142 start(helper->Starts()[t]),
1145 end(helper->Ends()[t]),
1146 size_min(helper->SizeMin(t)) {}
1160 const std::string& cut_name,
1162 std::vector<CachedIntervalData> events, IntegerValue capacity_max,
1165 const int num_events = events.size();
1166 if (num_events <= 1)
return;
1168 std::sort(events.begin(), events.end(),
1170 return e1.start_min < e2.start_min ||
1171 (e1.start_min == e2.start_min && e1.end_max < e2.end_max);
1183 const auto add_balas_disjunctive_cut =
1184 [&](
const std::string& local_cut_name, IntegerValue start_min_1,
1186 IntegerValue start_min_2, IntegerValue duration_min_2,
1189 if (start_min_2 >= start_min_1 + duration_min_1 ||
1190 start_min_1 >= start_min_2 + duration_min_2) {
1193 const IntegerValue coeff_1 = duration_min_1 + start_min_1 - start_min_2;
1194 const IntegerValue coeff_2 = duration_min_2 + start_min_2 - start_min_1;
1195 const IntegerValue rhs = duration_min_1 * duration_min_2 +
1196 duration_min_1 * start_min_2 +
1197 duration_min_2 * start_min_1;
1199 if (
ToDouble(coeff_1) * start_1.LpValue(lp_values) +
1200 ToDouble(coeff_2) * start_2.LpValue(lp_values) <=
1201 ToDouble(rhs) - kMinCutViolation) {
1203 cut.
AddTerm(start_1, coeff_1);
1204 cut.
AddTerm(start_2, coeff_2);
1205 top_n_cuts.
AddCut(cut.
Build(), local_cut_name, lp_values);
1209 for (
int i = 0; i + 1 < num_events; ++i) {
1211 for (
int j = i + 1; j < num_events; ++j) {
1221 if (interval_1_can_precede_2 && !interval_2_can_precede_1 &&
1229 absl::StrCat(cut_name,
"DetectedPrecedence"),
1231 }
else if (interval_2_can_precede_1 && !interval_1_can_precede_2 &&
1239 absl::StrCat(cut_name,
"DetectedPrecedence"),
1242 add_balas_disjunctive_cut(absl::StrCat(cut_name,
"DisjunctionOnStart"),
1245 add_balas_disjunctive_cut(absl::StrCat(cut_name,
"DisjunctionOnEnd"),
1261 AddIntegerVariableFromIntervals(helper,
model, &result.
vars);
1271 std::vector<CachedIntervalData> events;
1272 for (
int t = 0; t < helper->
NumTasks(); ++t) {
1275 event.demand_min = demands_helper->
DemandMin(t);
1276 events.push_back(event);
1281 "Cumulative", lp_values, std::move(events), capacity_max,
model,
1292 AddIntegerVariableFromIntervals(helper,
model, &result.
vars);
1301 std::vector<CachedIntervalData> events;
1302 for (
int t = 0; t < helper->
NumTasks(); ++t) {
1305 event.demand_min = IntegerValue(1);
1306 events.push_back(event);
1310 "NoOverlap", lp_values, std::move(events), IntegerValue(1),
model,
1327 ", y_max = ",
y_max.value(),
1343 bool ComputeWeightedSumOfEndMinsForOnePermutation(
1344 const std::vector<PermutableEvent>& events, IntegerValue capacity_max,
1345 IntegerValue& sum_of_ends, IntegerValue& sum_of_weighted_ends,
1346 std::vector<std::pair<IntegerValue, IntegerValue>>& profile,
1347 std::vector<std::pair<IntegerValue, IntegerValue>>& new_profile) {
1349 sum_of_weighted_ends = 0;
1359 std::max(event.x_start_min, start_of_previous_task);
1364 while (profile[current + 1].first <=
start_min ||
1365 profile[current].second < event.y_size_min) {
1369 const IntegerValue actual_start =
1371 start_of_previous_task = actual_start;
1374 if (actual_start > event.x_start_max)
return false;
1376 const IntegerValue actual_end = actual_start +
event.x_size_min;
1377 sum_of_ends += actual_end;
1378 sum_of_weighted_ends +=
event.y_size_min * actual_end;
1381 if (&event == &events.back())
break;
1384 new_profile.clear();
1385 new_profile.push_back(
1386 {actual_start, profile[current].second -
event.y_size_min});
1389 while (profile[current].first < actual_end) {
1390 new_profile.push_back(
1391 {profile[current].first, profile[current].second -
event.y_size_min});
1395 if (profile[current].first > actual_end) {
1396 new_profile.push_back(
1397 {actual_end, new_profile.back().second + event.y_size_min});
1399 while (current < profile.size()) {
1400 new_profile.push_back(profile[current]);
1403 profile.swap(new_profile);
1411 IntegerValue capacity_max,
1412 IntegerValue& min_sum_of_end_mins,
1413 IntegerValue& min_sum_of_weighted_end_mins,
1414 IntegerValue unweighted_threshold,
1415 IntegerValue weighted_threshold) {
1416 int num_explored = 0;
1422 std::vector<std::pair<IntegerValue, IntegerValue>> profile;
1423 std::vector<std::pair<IntegerValue, IntegerValue>> new_profile;
1425 IntegerValue sum_of_ends(0);
1426 IntegerValue sum_of_weighted_ends(0);
1427 if (ComputeWeightedSumOfEndMinsForOnePermutation(
1428 events, capacity_max, sum_of_ends, sum_of_weighted_ends, profile,
1430 min_sum_of_end_mins =
std::min(sum_of_ends, min_sum_of_end_mins);
1431 min_sum_of_weighted_end_mins =
1432 std::min(sum_of_weighted_ends, min_sum_of_weighted_end_mins);
1434 if (min_sum_of_end_mins <= unweighted_threshold &&
1435 min_sum_of_weighted_end_mins <= weighted_threshold) {
1441 }
while (std::next_permutation(events.begin(), events.end()));
1442 VLOG(2) <<
"DP: size=" << events.size() <<
", explored = " << num_explored
1443 <<
", pruned = " << num_pruned
1444 <<
", min_sum_of_end_mins = " << min_sum_of_end_mins
1445 <<
", min_sum_of_weighted_end_mins = "
1446 << min_sum_of_weighted_end_mins;
1447 return num_explored > 0;
1454 const std::string& cut_name,
1456 std::vector<CtEvent> events, IntegerValue capacity_max,
Model*
model,
1460 std::sort(events.begin(), events.end(),
1462 return std::tie(e1.x_start_min, e1.y_size_min, e1.x_lp_end) <
1463 std::tie(e2.x_start_min, e2.y_size_min, e2.x_lp_end);
1465 std::vector<PermutableEvent> permutable_events;
1469 events[
start].x_start_min == events[
start - 1].x_start_min) {
1473 const IntegerValue sequence_start_min = events[
start].x_start_min;
1474 std::vector<CtEvent> residual_tasks(events.begin() +
start, events.end());
1480 for (
int before = 0; before <
start; ++before) {
1481 if (events[before].x_start_min + events[before].x_size_min >
1482 sequence_start_min) {
1483 residual_tasks.push_back(events[before]);
1484 residual_tasks.back().lifted =
true;
1488 std::sort(residual_tasks.begin(), residual_tasks.end(),
1490 return e1.x_lp_end < e2.x_lp_end;
1493 IntegerValue sum_of_durations(0);
1494 IntegerValue sum_of_energies(0);
1495 double sum_of_ends_lp = 0.0;
1496 double sum_of_weighted_ends_lp = 0.0;
1497 IntegerValue sum_of_demands(0);
1499 permutable_events.clear();
1500 for (
int i = 0; i < std::min<int>(residual_tasks.size(), 7); ++i) {
1501 const CtEvent&
event = residual_tasks[i];
1502 permutable_events.emplace_back(i, event);
1504 sum_of_weighted_ends_lp +=
event.x_lp_end *
ToDouble(event.y_size_min);
1505 sum_of_demands +=
event.y_size_min;
1506 sum_of_durations +=
event.x_size_min;
1507 sum_of_energies +=
event.x_size_min *
event.y_size_min;
1512 if (i <= 1 || sum_of_demands <= capacity_max)
continue;
1516 for (
int j = 0; j <= i; ++j) {
1520 permutable_events[j].index = j;
1523 permutable_events, capacity_max, min_sum_of_end_mins,
1524 min_sum_of_weighted_end_mins,
1526 std::floor(sum_of_ends_lp + kMinCutViolation),
1528 std::floor(sum_of_weighted_ends_lp + kMinCutViolation))) {
1532 const double unweigthed_violation =
1533 (
ToDouble(min_sum_of_end_mins) - sum_of_ends_lp) /
1535 const double weighted_violation =
1536 (
ToDouble(min_sum_of_weighted_end_mins) - sum_of_weighted_ends_lp) /
1540 if (unweigthed_violation > weighted_violation &&
1541 unweigthed_violation > kMinCutViolation) {
1544 bool is_lifted =
false;
1545 for (
int j = 0; j <= i; ++j) {
1546 const CtEvent&
event = residual_tasks[j];
1547 is_lifted |=
event.
lifted;
1548 cut.
AddTerm(event.x_end, IntegerValue(1));
1550 std::string full_name = cut_name;
1551 top_n_cuts.
AddCut(cut.
Build(), full_name, lp_values);
1555 if (weighted_violation >= unweigthed_violation &&
1556 weighted_violation > kMinCutViolation) {
1559 bool is_lifted =
false;
1560 for (
int j = 0; j <= i; ++j) {
1561 const CtEvent&
event = residual_tasks[j];
1562 is_lifted |=
event.
lifted;
1563 cut.
AddTerm(event.x_end, event.y_size_min);
1565 std::string full_name = cut_name +
"_weighted";
1566 if (is_lifted) full_name.append(
"_lifted");
1567 top_n_cuts.
AddCut(cut.
Build(), full_name, lp_values);
1589 const std::string& cut_name,
1591 std::vector<CtEvent> events,
bool use_lifting,
bool skip_low_sizes,
1596 std::sort(events.begin(), events.end(),
1598 return std::tie(e1.x_start_min, e1.y_size_min, e1.x_lp_end) <
1599 std::tie(e2.x_start_min, e2.y_size_min, e2.x_lp_end);
1604 events[
start].x_start_min == events[
start - 1].x_start_min) {
1608 const IntegerValue sequence_start_min = events[
start].x_start_min;
1609 std::vector<CtEvent> residual_tasks(events.begin() +
start, events.end());
1617 for (
int before = 0; before <
start; ++before) {
1618 if (events[before].x_start_min + events[before].x_size_min >
1619 sequence_start_min) {
1621 CtEvent event = events[before];
1624 event.x_start_min, event.x_start_max, event.x_end_min,
1625 event.x_end_max, event.x_size_min, event.y_size_min,
1626 event.decomposed_energy, sequence_start_min, event.x_end_max);
1628 event.x_size_min +
event.x_start_min - sequence_start_min;
1629 event.x_start_min = sequence_start_min;
1630 if (event.energy_min > event.x_size_min * event.y_size_min) {
1631 event.use_energy =
true;
1633 DCHECK_GE(event.energy_min, event.x_size_min * event.y_size_min);
1634 if (event.energy_min <= 0)
continue;
1635 residual_tasks.push_back(event);
1640 std::sort(residual_tasks.begin(), residual_tasks.end(),
1642 return e1.x_lp_end < e2.x_lp_end;
1646 double best_efficacy = 0.01;
1647 IntegerValue best_min_contrib(0);
1648 IntegerValue sum_duration(0);
1649 IntegerValue sum_square_duration(0);
1650 IntegerValue best_capacity(0);
1651 double unscaled_lp_contrib = 0.0;
1658 for (
int i = 0; i < residual_tasks.size(); ++i) {
1659 const CtEvent&
event = residual_tasks[i];
1660 DCHECK_GE(event.x_start_min, sequence_start_min);
1661 const IntegerValue
energy =
event.energy_min;
1665 current_start_min =
std::min(current_start_min, event.x_start_min);
1669 if (skip_low_sizes && i < 7)
continue;
1677 y_min =
std::min(y_min, event.y_min);
1678 y_max =
std::max(y_max, event.y_max);
1679 if (!event.y_size_is_fixed) use_dp =
false;
1684 if (y_max - y_min != dp.
Bound()) {
1690 dp.
Add(event.y_size_min.value());
1694 use_dp ? IntegerValue(dp.
CurrentMax()) : y_max - y_min;
1702 sum_square_duration.value()))) {
1705 const IntegerValue min_contrib =
1706 (sum_duration * sum_duration + sum_square_duration) / 2 +
1707 current_start_min * sum_duration *
capacity;
1711 const double efficacy =
1713 std::sqrt(
ToDouble(sum_square_duration));
1716 if (efficacy > best_efficacy) {
1717 best_efficacy = efficacy;
1719 best_min_contrib = min_contrib;
1723 if (best_end != -1) {
1725 bool is_lifted =
false;
1726 bool add_energy_to_name =
false;
1727 for (
int i = 0; i <= best_end; ++i) {
1728 const CtEvent&
event = residual_tasks[i];
1729 is_lifted |=
event.
lifted;
1730 add_energy_to_name |=
event.use_energy;
1731 cut.
AddTerm(event.x_end, event.energy_min * best_capacity);
1733 std::string full_name = cut_name;
1734 if (is_lifted) full_name.append(
"_lifted");
1735 if (add_energy_to_name) full_name.append(
"_energy");
1736 top_n_cuts.
AddCut(cut.
Build(), full_name, lp_values);
1746 AddIntegerVariableFromIntervals(helper,
model, &result.
vars);
1755 auto generate_cuts = [&lp_values,
model, manager, helper](
bool mirror) {
1756 std::vector<CtEvent> events;
1763 event.x_end = end_expr;
1764 event.x_lp_end = end_expr.
LpValue(lp_values);
1765 event.y_min = IntegerValue(0);
1766 event.y_max = IntegerValue(1);
1767 event.y_size_min = IntegerValue(1);
1768 event.energy_min = size_min;
1769 events.push_back(event);
1773 const std::string mirror_str = mirror ?
"Mirror" :
"";
1775 absl::StrCat(
"NoOverlapCompletionTimeExhaustive", mirror_str),
1776 lp_values, events, IntegerValue(1),
model, manager);
1779 absl::StrCat(
"NoOverlapCompletionTimeQueyrane", mirror_str),
1780 lp_values, std::move(events),
1781 true,
true,
model, manager);
1784 generate_cuts(
false);
1786 generate_cuts(
true);
1798 AddIntegerVariableFromIntervals(helper,
model, &result.
vars);
1810 auto generate_cuts = [&lp_values,
model, manager, helper,
1811 demands_helper, capacity_max](
bool mirror) {
1812 std::vector<CtEvent> events;
1814 if (!helper->IsPresent(
index))
continue;
1815 if (helper->SizeMin(
index) > 0 &&
1816 demands_helper->DemandMin(
index) > 0) {
1818 event.x_end = helper->Ends()[
index];
1819 event.x_lp_end =
event.x_end.LpValue(lp_values);
1820 event.y_min = IntegerValue(0);
1821 event.y_max = IntegerValue(capacity_max);
1822 event.y_size_min = demands_helper->DemandMin(
index);
1823 event.energy_min = demands_helper->EnergyMin(
index);
1824 event.decomposed_energy =
1825 demands_helper->DecomposedEnergies()[
index];
1826 event.y_size_is_fixed = demands_helper->DemandIsFixed(
index);
1827 events.push_back(event);
1831 const std::string mirror_str = mirror ?
"Mirror" :
"";
1833 absl::StrCat(
"CumulativeCompletionTimeExhaustive", mirror_str),
1834 lp_values, events, capacity_max,
model, manager);
1837 absl::StrCat(
"CumulativeCompletionTimeQueyrane", mirror_str),
1838 lp_values, std::move(events),
1839 true,
true,
model, manager);
1841 if (!helper->SynchronizeAndSetTimeDirection(
true))
return false;
1842 generate_cuts(
false);
1843 if (!helper->SynchronizeAndSetTimeDirection(
false))
return false;
1844 generate_cuts(
true);
1852 const std::vector<IntervalVariable>& x_intervals,
1853 const std::vector<IntervalVariable>& y_intervals,
Model*
model) {
1861 AddIntegerVariableFromIntervals(x_helper,
model, &result.
vars);
1862 AddIntegerVariableFromIntervals(y_helper,
model, &result.
vars);
1866 [x_helper, y_helper,
model](
1872 const int num_rectangles = x_helper->
NumTasks();
1873 std::vector<int> active_rectangles;
1874 std::vector<IntegerValue> cached_areas(num_rectangles);
1875 std::vector<Rectangle> cached_rectangles(num_rectangles);
1876 for (
int rect = 0; rect < num_rectangles; ++rect) {
1880 cached_areas[rect] =
1882 if (cached_areas[rect] == 0)
continue;
1888 Rectangle& rectangle = cached_rectangles[rect];
1889 rectangle.x_min = x_helper->
StartMin(rect);
1890 rectangle.x_max = x_helper->
EndMax(rect);
1891 rectangle.y_min = y_helper->
StartMin(rect);
1892 rectangle.y_max = y_helper->
EndMax(rect);
1894 active_rectangles.push_back(rect);
1897 if (active_rectangles.size() <= 1)
return true;
1899 std::vector<absl::Span<int>> components =
1901 cached_rectangles, absl::MakeSpan(active_rectangles));
1902 for (absl::Span<int> rectangles : components) {
1903 if (rectangles.size() <= 1)
continue;
1905 auto generate_cuts = [&lp_values,
model, manager, &rectangles,
1907 const std::string& cut_name,
1910 std::vector<CtEvent> events;
1912 for (
const int rect : rectangles) {
1913 CtEvent event(rect, x_helper);
1914 event.x_end = x_helper->Ends()[rect];
1915 event.x_lp_end =
event.x_end.LpValue(lp_values);
1916 event.y_min = y_helper->
StartMin(rect);
1917 event.y_max = y_helper->
EndMax(rect);
1918 event.y_size_min = y_helper->
SizeMin(rect);
1921 event.energy_min =
event.x_size_min *
event.y_size_min;
1923 x_helper->Sizes()[rect], y_helper->
Sizes()[rect],
model);
1924 events.push_back(event);
1928 cut_name, lp_values, std::move(events),
1929 false,
false,
model,
1933 if (!x_helper->SynchronizeAndSetTimeDirection(
true))
return false;
1935 generate_cuts(
"NoOverlap2dXCompletionTime", x_helper, y_helper);
1936 generate_cuts(
"NoOverlap2dYCompletionTime", y_helper, x_helper);
1937 if (!x_helper->SynchronizeAndSetTimeDirection(
false))
return false;
1939 generate_cuts(
"NoOverlap2dXCompletionTimeMirror", x_helper, y_helper);
1940 generate_cuts(
"NoOverlap2dYCompletionTimeMirror", y_helper, x_helper);
An Assignment is a variable -> domains mapping, used to report solutions to the user.
bool LimitReached() const
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
void AddRectangle(IntegerValue x_min, IntegerValue x_max, IntegerValue y_min, IntegerValue y_max)
IntegerValue GetBoundingArea()
ABSL_MUST_USE_RESULT bool LiteralOrNegationHasView(Literal lit, IntegerVariable *view=nullptr, bool *view_is_direct=nullptr) const
bool IsFixed(IntegerVariable i) const
IntegerValue UpperBound(IntegerVariable i) const
IntegerValue FixedValue(IntegerVariable i) const
IntegerValue LowerBound(IntegerVariable i) const
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff=IntegerValue(1))
ABSL_MUST_USE_RESULT bool AddDecomposedProduct(const std::vector< LiteralValueValue > &product)
void AddConstant(IntegerValue value)
void AddLinearExpression(const LinearExpression &expr)
LinearExpression BuildExpression()
void AddTerm(IntegerVariable var, IntegerValue coeff)
void AddQuadraticLowerBound(AffineExpression left, AffineExpression right, IntegerTrail *integer_trail, bool *is_quadratic=nullptr)
LiteralIndex Index() const
int64_t CurrentMax() const
void AddChoices(absl::Span< const int64_t > choices)
void Reset(int64_t bound)
Class that owns everything related to a particular optimization model.
IntegerValue EndMin(int t) const
bool IsPresent(int t) const
bool IsAbsent(int t) const
IntegerValue EndMax(int t) const
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
IntegerValue StartMin(int t) const
Literal PresenceLiteral(int index) const
IntegerValue StartMax(int t) const
const std::vector< AffineExpression > & Sizes() const
IntegerValue SizeMax(int t) const
IntegerValue SizeMin(int t) const
const std::vector< AffineExpression > & Ends() const
bool EnergyIsQuadratic(int t) const
ABSL_MUST_USE_RESULT bool AddLinearizedDemand(int t, LinearConstraintBuilder *builder) const
const std::vector< std::vector< LiteralValueValue > > & DecomposedEnergies() const
IntegerValue DemandMax(int t) const
IntegerValue EnergyMin(int t) const
void CacheAllEnergyValues()
const std::vector< AffineExpression > & Demands() const
IntegerValue DemandMin(int t) const
void AddCut(LinearConstraint ct, const std::string &name, const absl::StrongVector< IntegerVariable, double > &lp_solution)
void TransferToManager(const absl::StrongVector< IntegerVariable, double > &lp_solution, LinearConstraintManager *manager)
ModelSharedTimeLimit * time_limit
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
static double ToDouble(double f)
CutGenerator CreateCumulativeEnergyCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, const std::optional< AffineExpression > &makespan, Model *model)
bool ComputeMinSumOfWeightedEndMins(std::vector< PermutableEvent > &events, IntegerValue capacity_max, IntegerValue &min_sum_of_end_mins, IntegerValue &min_sum_of_weighted_end_mins, IntegerValue unweighted_threshold, IntegerValue weighted_threshold)
CutGenerator CreateNoOverlap2dEnergyCutGenerator(const std::vector< IntervalVariable > &x_intervals, const std::vector< IntervalVariable > &y_intervals, Model *model)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
CutGenerator CreateNoOverlapCompletionTimeCutGenerator(SchedulingConstraintHelper *helper, Model *model)
const LiteralIndex kNoLiteralIndex(-1)
void GenerateShortCompletionTimeCutsWithExactBound(const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, std::vector< CtEvent > events, IntegerValue capacity_max, Model *model, LinearConstraintManager *manager)
std::vector< absl::Span< int > > GetOverlappingRectangleComponents(const std::vector< Rectangle > &rectangles, absl::Span< int > active_rectangles)
void GenerateCumulativeEnergeticCutsWithMakespanAndFixedCapacity(const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, std::vector< EnergyEvent > events, IntegerValue capacity, AffineExpression makespan, TimeLimit *time_limit, Model *model, LinearConstraintManager *manager)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
CutGenerator CreateCumulativePrecedenceCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
CutGenerator CreateCumulativeCompletionTimeCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
void GenerateCutsBetweenPairOfNonOverlappingTasks(const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, std::vector< CachedIntervalData > events, IntegerValue capacity_max, Model *model, LinearConstraintManager *manager)
std::function< IntegerVariable(Model *)> NewIntegerVariableFromLiteral(Literal lit)
CutGenerator CreateNoOverlap2dCompletionTimeCutGenerator(const std::vector< IntervalVariable > &x_intervals, const std::vector< IntervalVariable > &y_intervals, Model *model)
void GenerateCompletionTimeCutsWithEnergy(const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, std::vector< CtEvent > events, bool use_lifting, bool skip_low_sizes, Model *model, LinearConstraintManager *manager)
CutGenerator CreateCumulativeTimeTableCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
IntegerValue ComputeEnergyMinInWindow(IntegerValue start_min, IntegerValue start_max, IntegerValue end_min, IntegerValue end_max, IntegerValue size_min, IntegerValue demand_min, const std::vector< LiteralValueValue > &filtered_energy, IntegerValue window_start, IntegerValue window_end)
void AppendVariablesToCumulativeCut(const AffineExpression &capacity, SchedulingDemandHelper *demands_helper, Model *model, std::vector< IntegerVariable > *vars)
void GenerateNoOverlap2dEnergyCut(const std::vector< std::vector< LiteralValueValue >> &energies, absl::Span< int > rectangles, const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, Model *model, LinearConstraintManager *manager, SchedulingConstraintHelper *x_helper, SchedulingConstraintHelper *y_helper, SchedulingDemandHelper *y_demands_helper)
void GenerateCumulativeEnergeticCuts(const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, std::vector< EnergyEvent > events, const AffineExpression capacity, TimeLimit *time_limit, Model *model, LinearConstraintManager *manager)
std::vector< LiteralValueValue > TryToDecomposeProduct(const AffineExpression &left, const AffineExpression &right, Model *model)
CutGenerator CreateNoOverlapEnergyCutGenerator(SchedulingConstraintHelper *helper, const std::optional< AffineExpression > &makespan, Model *model)
double ToDouble(IntegerValue value)
CutGenerator CreateNoOverlapPrecedenceCutGenerator(SchedulingConstraintHelper *helper, Model *model)
Collection of objects used to extend the Constraint Solver library.
bool AtMinOrMaxInt64(int64_t x)
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapProd(int64_t x, int64_t y)
std::optional< int64_t > end
AffineExpression Negated() const
double LpValue(const absl::StrongVector< IntegerVariable, double > &lp_values) const
const std::string DebugString() const
std::vector< LiteralValueValue > decomposed_energy
BaseEvent(int t, SchedulingConstraintHelper *x_helper)
CachedIntervalData(int t, SchedulingConstraintHelper *helper)
std::string DebugString() const
bool only_run_at_level_zero
std::vector< IntegerVariable > vars
std::function< bool(const absl::StrongVector< IntegerVariable, double > &lp_values, LinearConstraintManager *manager)> generate_cuts
EnergyEvent(int t, SchedulingConstraintHelper *x_helper)
LiteralIndex presence_literal_index
LinearExpression linearized_energy
std::string DebugString() const
IntegerValue GetMinOverlap(IntegerValue start, IntegerValue end) const
double linearized_energy_lp_value
ABSL_MUST_USE_RESULT bool FillEnergyLp(AffineExpression x_size, const absl::StrongVector< IntegerVariable, double > &lp_values, Model *model)
double LpValue(const absl::StrongVector< IntegerVariable, double > &lp_values) const
#define VLOG(verboselevel)