28#include "absl/base/attributes.h"
29#include "absl/container/btree_set.h"
30#include "absl/container/flat_hash_map.h"
31#include "absl/log/check.h"
32#include "absl/strings/str_cat.h"
33#include "absl/strings/string_view.h"
34#include "absl/types/span.h"
58const double kMinCutViolation = 1e-4;
95 IntegerValue
GetMinOverlap(IntegerValue start, IntegerValue end)
const {
133 ", y_size = ",
y_size.DebugString(),
", energy = ",
146ABSL_MUST_USE_RESULT
bool AddOneEvent(
147 const EnergyEvent& event, IntegerValue window_start,
148 IntegerValue window_end, LinearConstraintBuilder* cut,
149 bool* add_energy_to_name =
nullptr,
bool* add_quadratic_to_name =
nullptr,
150 bool* add_opt_to_name =
nullptr,
bool* add_lifted_to_name =
nullptr) {
151 DCHECK(cut !=
nullptr);
153 if (event.x_end_min <= window_start || event.x_start_max >= window_end) {
157 if (event.x_start_min >= window_start && event.x_end_max <= window_end) {
159 cut->AddLinearExpression(event.linearized_energy);
161 if (event.energy_is_quadratic && add_quadratic_to_name !=
nullptr) {
162 *add_quadratic_to_name =
true;
164 if (add_energy_to_name !=
nullptr &&
165 event.energy_min > event.x_size_min * event.y_size_min) {
166 *add_energy_to_name =
true;
168 if (!event.IsPresent() && add_opt_to_name !=
nullptr) {
169 *add_opt_to_name =
true;
172 const IntegerValue min_overlap =
173 event.GetMinOverlap(window_start, window_end);
174 if (min_overlap <= 0)
return true;
175 if (add_lifted_to_name !=
nullptr) *add_lifted_to_name =
true;
177 if (event.IsPresent()) {
178 const std::vector<LiteralValueValue>& energy =
event.decomposed_energy;
179 if (energy.empty()) {
180 cut->AddTerm(event.y_size, min_overlap);
182 const IntegerValue window_size = window_end - window_start;
183 for (
const auto [lit, fixed_size, fixed_demand] : energy) {
184 const IntegerValue alt_end_min =
185 std::max(event.x_end_min, event.x_start_min + fixed_size);
186 const IntegerValue alt_start_max =
187 std::min(event.x_start_max, event.x_end_max - fixed_size);
188 const IntegerValue energy_min =
190 std::min({alt_end_min - window_start, window_end - alt_start_max,
191 fixed_size, window_size});
192 if (energy_min == 0)
continue;
193 if (!cut->AddLiteralTerm(lit, energy_min))
return false;
195 if (add_energy_to_name !=
nullptr) *add_energy_to_name =
true;
198 if (add_opt_to_name !=
nullptr) *add_opt_to_name =
true;
200 event.x_start_min, event.x_start_max, event.x_end_min,
201 event.x_end_max, event.x_size_min, event.y_size_min,
202 event.decomposed_energy, window_start, window_end);
203 if (min_energy > event.x_size_min * event.y_size_min &&
204 add_energy_to_name !=
nullptr) {
205 *add_energy_to_name =
true;
207 if (!cut->AddLiteralTerm(
Literal(event.presence_literal_index),
218std::vector<int64_t> FindPossibleDemands(
const EnergyEvent& event,
221 std::vector<int64_t> possible_demands;
222 if (event.decomposed_energy.empty()) {
223 if (integer_trail->IsFixed(event.y_size)) {
224 possible_demands.push_back(
225 integer_trail->FixedValue(event.y_size).value());
227 if (integer_trail->InitialVariableDomain(event.y_size.var).Size() >
231 for (
const int64_t var_value :
232 integer_trail->InitialVariableDomain(event.y_size.var).Values()) {
233 possible_demands.push_back(event.y_size.ValueAt(var_value).value());
237 for (
const auto [lit, fixed_size, fixed_demand] : event.decomposed_energy) {
238 if (assignment.LiteralIsFalse(lit))
continue;
239 possible_demands.push_back(fixed_demand.value());
242 return possible_demands;
248 absl::Span<const EnergyEvent> events, IntegerValue window_start,
249 IntegerValue window_end,
double available_energy_lp,
250 const util_intops::StrongVector<IntegerVariable, double>& lp_values,
252 temp_builder->Clear();
254 if (!AddOneEvent(event, window_start, window_end, temp_builder)) {
258 return temp_builder->BuildExpression().LpValue(lp_values) >=
259 available_energy_lp * (1.0 + kMinCutViolation);
277 absl::string_view cut_name,
279 std::vector<EnergyEvent> events, IntegerValue capacity,
284 DCHECK(integer_trail->
IsFixed(capacity));
296 struct OverloadedTimeWindowWithMakespan {
299 IntegerValue fixed_energy_rhs;
300 bool use_makespan =
false;
301 bool use_subset_sum =
false;
304 std::vector<OverloadedTimeWindowWithMakespan> overloaded_time_windows;
309 std::vector<IntegerValue> time_points;
310 std::vector<std::vector<int64_t>> possible_demands(events.size());
311 const IntegerValue makespan_min = integer_trail->
LowerBound(makespan);
314 for (
int i = 0;
i < events.size(); ++
i) {
316 if (event.x_start_min < makespan_min) {
317 time_points.push_back(event.x_start_min);
319 if (event.x_start_max < makespan_min) {
320 time_points.push_back(event.x_start_max);
322 if (event.x_end_min < makespan_min) {
323 time_points.push_back(event.x_end_min);
325 if (event.x_end_max < makespan_min) {
326 time_points.push_back(event.x_end_max);
328 max_end_min = std::max(max_end_min, event.x_end_min);
329 max_end_max = std::max(max_end_max, event.x_end_max);
330 possible_demands[
i] = FindPossibleDemands(event, assignment, integer_trail);
332 time_points.push_back(makespan_min);
333 time_points.push_back(max_end_max);
336 const int num_time_points = time_points.size();
337 absl::flat_hash_map<IntegerValue, IntegerValue> reachable_capacity_ending_at;
340 for (
int i = 1;
i < num_time_points; ++
i) {
341 const IntegerValue window_start = time_points[
i - 1];
342 const IntegerValue window_end = time_points[
i];
343 reachable_capacity_subset_sum.
Reset(capacity.value());
344 for (
int i = 0;
i < events.size(); ++
i) {
346 if (event.x_start_min >= window_end || event.x_end_max <= window_start) {
349 if (possible_demands[
i].empty()) {
351 reachable_capacity_subset_sum.
Add(capacity.value());
353 reachable_capacity_subset_sum.
AddChoices(possible_demands[
i]);
355 if (reachable_capacity_subset_sum.
CurrentMax() == capacity.value())
break;
357 reachable_capacity_ending_at[window_end] =
361 const double capacity_lp =
ToDouble(capacity);
362 const double makespan_lp = makespan.
LpValue(lp_values);
363 const double makespan_min_lp =
ToDouble(makespan_min);
365 for (
int i = 0;
i + 1 < num_time_points; ++
i) {
367 if (events.size() > 50 &&
time_limit->LimitReached())
return;
369 const IntegerValue window_start = time_points[
i];
371 if (window_start >= max_end_min)
break;
373 IntegerValue cumulated_max_energy = 0;
374 IntegerValue cumulated_max_energy_before_makespan_min = 0;
375 bool use_subset_sum =
false;
376 bool use_subset_sum_before_makespan_min =
false;
378 for (
int j =
i + 1; j < num_time_points; ++j) {
379 const IntegerValue strip_start = time_points[j - 1];
380 const IntegerValue window_end = time_points[j];
381 const IntegerValue max_reachable_capacity_in_current_strip =
382 reachable_capacity_ending_at[window_end];
383 DCHECK_LE(max_reachable_capacity_in_current_strip, capacity);
386 if (max_reachable_capacity_in_current_strip < capacity) {
387 use_subset_sum =
true;
388 if (window_end <= makespan_min) {
389 use_subset_sum_before_makespan_min =
true;
393 const IntegerValue energy_in_strip =
394 (window_end - strip_start) * max_reachable_capacity_in_current_strip;
395 cumulated_max_energy += energy_in_strip;
396 if (window_end <= makespan_min) {
397 cumulated_max_energy_before_makespan_min += energy_in_strip;
400 if (window_start >= makespan_min) {
401 DCHECK_EQ(cumulated_max_energy_before_makespan_min, 0);
403 DCHECK_LE(cumulated_max_energy, capacity * (window_end - window_start));
404 const double max_energy_up_to_makespan_lp =
405 strip_start >= makespan_min
406 ?
ToDouble(cumulated_max_energy_before_makespan_min) +
407 (makespan_lp - makespan_min_lp) * capacity_lp
408 : std::numeric_limits<double>::infinity();
415 const bool use_makespan =
416 max_energy_up_to_makespan_lp <=
417 ToDouble(cumulated_max_energy) + kMinCutViolation;
418 const double available_energy_lp = use_makespan
419 ? max_energy_up_to_makespan_lp
421 if (CutIsEfficient(events, window_start, window_end, available_energy_lp,
422 lp_values, &temp_builder)) {
423 OverloadedTimeWindowWithMakespan
w;
424 w.start = window_start;
426 w.fixed_energy_rhs = use_makespan
427 ? cumulated_max_energy_before_makespan_min
428 : cumulated_max_energy;
429 w.use_makespan = use_makespan;
431 use_makespan ? use_subset_sum_before_makespan_min : use_subset_sum;
432 overloaded_time_windows.push_back(std::move(
w));
437 if (overloaded_time_windows.empty())
return;
439 VLOG(3) <<
"GenerateCumulativeEnergeticCutsWithMakespanAndFixedCapacity: "
440 << events.size() <<
" events, " << time_points.size()
441 <<
" time points, " << overloaded_time_windows.size()
442 <<
" overloads detected";
445 for (
const auto&
w : overloaded_time_windows) {
446 bool cut_generated =
true;
447 bool add_opt_to_name =
false;
448 bool add_lifted_to_name =
false;
449 bool add_quadratic_to_name =
false;
450 bool add_energy_to_name =
false;
453 if (
w.use_makespan) {
455 cut.
AddTerm(makespan, -capacity);
460 if (!AddOneEvent(event,
w.start,
w.end, &cut, &add_energy_to_name,
461 &add_quadratic_to_name, &add_opt_to_name,
462 &add_lifted_to_name)) {
463 cut_generated =
false;
469 std::string full_name(cut_name);
470 if (add_opt_to_name) full_name.append(
"_optional");
471 if (add_quadratic_to_name) full_name.append(
"_quadratic");
472 if (add_lifted_to_name) full_name.append(
"_lifted");
473 if (add_energy_to_name) full_name.append(
"_energy");
474 if (
w.use_makespan) full_name.append(
"_makespan");
475 if (
w.use_subset_sum) full_name.append(
"_subsetsum");
476 top_n_cuts.
AddCut(cut.
Build(), full_name, lp_values);
484 absl::string_view cut_name,
488 double max_possible_energy_lp = 0.0;
490 max_possible_energy_lp +=
event.linearized_energy_lp_value;
500 struct OverloadedTimeWindow {
504 std::vector<OverloadedTimeWindow> overloaded_time_windows;
505 const double capacity_lp = capacity.
LpValue(lp_values);
509 absl::btree_set<IntegerValue> time_points_set;
512 time_points_set.insert(event.x_start_min);
513 time_points_set.insert(event.x_start_max);
514 time_points_set.insert(event.x_end_min);
515 time_points_set.insert(event.x_end_max);
516 max_end_min = std::max(max_end_min, event.x_end_min);
518 const std::vector<IntegerValue> time_points(time_points_set.begin(),
519 time_points_set.end());
520 const int num_time_points = time_points.size();
523 for (
int i = 0;
i + 1 < num_time_points; ++
i) {
525 if (events.size() > 50 &&
time_limit->LimitReached())
return;
527 const IntegerValue window_start = time_points[
i];
529 if (window_start >= max_end_min)
break;
531 for (
int j =
i + 1; j < num_time_points; ++j) {
532 const IntegerValue window_end = time_points[j];
533 const double available_energy_lp =
534 ToDouble(window_end - window_start) * capacity_lp;
535 if (available_energy_lp >= max_possible_energy_lp)
break;
536 if (CutIsEfficient(events, window_start, window_end, available_energy_lp,
537 lp_values, &temp_builder)) {
538 overloaded_time_windows.push_back({window_start, window_end});
543 if (overloaded_time_windows.empty())
return;
545 VLOG(3) <<
"GenerateCumulativeEnergeticCuts: " << events.size() <<
" events, "
546 << time_points.size() <<
" time points, "
547 << overloaded_time_windows.size() <<
" overloads detected";
550 for (
const auto& [window_start, window_end] : overloaded_time_windows) {
551 bool cut_generated =
true;
552 bool add_opt_to_name =
false;
553 bool add_lifted_to_name =
false;
554 bool add_quadratic_to_name =
false;
555 bool add_energy_to_name =
false;
559 cut.
AddTerm(capacity, window_start - window_end);
563 if (!AddOneEvent(event, window_start, window_end, &cut,
564 &add_energy_to_name, &add_quadratic_to_name,
565 &add_opt_to_name, &add_lifted_to_name)) {
566 cut_generated =
false;
572 std::string full_name(cut_name);
573 if (add_opt_to_name) full_name.append(
"_optional");
574 if (add_quadratic_to_name) full_name.append(
"_quadratic");
575 if (add_lifted_to_name) full_name.append(
"_lifted");
576 if (add_energy_to_name) full_name.append(
"_energy");
577 top_n_cuts.
AddCut(cut.
Build(), full_name, lp_values);
587 const std::optional<AffineExpression>& makespan,
Model* model) {
593 helper, model, &result.
vars,
595 if (makespan.has_value() && !makespan.value().IsConstant()) {
596 result.
vars.push_back(makespan.value().var);
602 result.
generate_cuts = [makespan, capacity, demands_helper, helper,
608 const auto& lp_values = manager->LpValues();
609 std::vector<EnergyEvent> events;
631 if (makespan.has_value() && integer_trail->
IsFixed(capacity)) {
633 "CumulativeEnergyM", lp_values, events,
649 const std::optional<AffineExpression>& makespan,
Model* model) {
653 helper, model, &result.
vars,
655 if (makespan.has_value() && !makespan.value().IsConstant()) {
656 result.
vars.push_back(makespan.value().var);
665 const auto& lp_values = manager->LpValues();
666 std::vector<EnergyEvent> events;
669 if (helper->
SizeMin(
i) == 0)
continue;
672 e.
y_size = IntegerValue(1);
683 if (makespan.has_value()) {
685 "NoOverlapEnergyM", lp_values, events,
686 IntegerValue(1), makespan.value(),
time_limit, model,
709 struct TimeTableEvent {
713 double demand_lp = 0.0;
714 bool is_positive =
false;
715 bool use_energy =
false;
716 bool is_optional =
false;
725 std::vector<TimeTableEvent> events;
726 const auto& lp_values = manager->LpValues();
727 if (lp_values.empty())
return true;
728 const double capacity_lp = capacity.
LpValue(lp_values);
735 const IntegerValue start_max = helper->
StartMax(
i);
736 const IntegerValue end_min = helper->
EndMin(
i);
738 if (start_max >= end_min)
continue;
741 e1.interval_index =
i;
749 e1.demand_lp = e1.demand.LpValue(lp_values);
750 e1.is_positive =
true;
754 TimeTableEvent e2 = e1;
756 e2.is_positive =
false;
758 events.push_back(e1);
759 events.push_back(e2);
765 std::sort(events.begin(), events.end(),
766 [](
const TimeTableEvent&
i,
const TimeTableEvent& j) {
767 if (i.time == j.time) {
768 if (i.is_positive == j.is_positive) {
769 return i.interval_index < j.interval_index;
771 return !i.is_positive;
773 return i.time < j.time;
776 double sum_of_demand_lp = 0.0;
777 bool positive_event_added_since_last_check =
false;
778 for (
int i = 0;
i < events.size(); ++
i) {
779 const TimeTableEvent& e = events[
i];
781 positive_event_added_since_last_check =
true;
782 sum_of_demand_lp += e.demand_lp;
786 if (positive_event_added_since_last_check) {
789 positive_event_added_since_last_check =
false;
791 if (sum_of_demand_lp >= capacity_lp + kMinCutViolation) {
793 bool use_energy =
false;
794 bool use_optional =
false;
796 cut.
AddTerm(capacity, IntegerValue(-1));
799 DCHECK(!events[
i].is_positive);
800 const IntegerValue time_point = events[
i - 1].time;
802 for (
int j = 0; j <
i; ++j) {
803 const TimeTableEvent& cut_event = events[j];
804 const int t = cut_event.interval_index;
805 DCHECK_LE(helper->
StartMax(t), time_point);
806 if (!cut_event.is_positive || helper->
EndMin(t) <= time_point) {
811 use_energy |= cut_event.use_energy;
812 use_optional |= cut_event.is_optional;
815 std::string cut_name =
"CumulativeTimeTable";
816 if (use_optional) cut_name +=
"_optional";
817 if (use_energy) cut_name +=
"_energy";
818 top_n_cuts.AddCut(cut.
Build(), cut_name, lp_values);
824 sum_of_demand_lp -= e.demand_lp;
826 top_n_cuts.TransferToManager(manager);
840 start(helper->Starts()[t]),
843 end(helper->Ends()[t]),
858 absl::string_view cut_name,
860 std::vector<CachedIntervalData> events, IntegerValue capacity_max,
863 const int num_events = events.size();
864 if (num_events <= 1)
return;
866 std::sort(events.begin(), events.end(),
868 return e1.start_min < e2.start_min ||
869 (e1.start_min == e2.start_min && e1.end_max < e2.end_max);
881 const auto add_balas_disjunctive_cut =
882 [&](absl::string_view local_cut_name, IntegerValue start_min_1,
884 IntegerValue start_min_2, IntegerValue duration_min_2,
887 if (start_min_2 >= start_min_1 + duration_min_1 ||
888 start_min_1 >= start_min_2 + duration_min_2) {
891 const IntegerValue coeff_1 = duration_min_1 + start_min_1 - start_min_2;
892 const IntegerValue coeff_2 = duration_min_2 + start_min_2 - start_min_1;
893 const IntegerValue rhs = duration_min_1 * duration_min_2 +
894 duration_min_1 * start_min_2 +
895 duration_min_2 * start_min_1;
897 if (
ToDouble(coeff_1) * start_1.LpValue(lp_values) +
898 ToDouble(coeff_2) * start_2.LpValue(lp_values) <=
903 top_n_cuts.
AddCut(cut.
Build(), local_cut_name, lp_values);
907 for (
int i = 0;
i + 1 < num_events; ++
i) {
909 for (
int j =
i + 1; j < num_events; ++j) {
919 if (interval_1_can_precede_2 && !interval_2_can_precede_1 &&
927 absl::StrCat(cut_name,
"DetectedPrecedence"),
929 }
else if (interval_2_can_precede_1 && !interval_1_can_precede_2 &&
937 absl::StrCat(cut_name,
"DetectedPrecedence"),
940 add_balas_disjunctive_cut(absl::StrCat(cut_name,
"DisjunctionOnStart"),
943 add_balas_disjunctive_cut(absl::StrCat(cut_name,
"DisjunctionOnEnd"),
961 helper, model, &result.
vars,
966 result.
generate_cuts = [integer_trail, helper, demands_helper, capacity,
970 std::vector<CachedIntervalData> events;
971 for (
int t = 0; t < helper->
NumTasks(); ++t) {
974 event.demand_min = demands_helper->
DemandMin(t);
975 events.push_back(event);
978 const IntegerValue capacity_max = integer_trail->
UpperBound(capacity);
980 "Cumulative", manager->LpValues(), std::move(events), capacity_max,
992 helper, model, &result.
vars,
999 std::vector<CachedIntervalData> events;
1000 for (
int t = 0; t < helper->
NumTasks(); ++t) {
1003 event.demand_min = IntegerValue(1);
1004 events.push_back(event);
1008 "NoOverlap", manager->LpValues(), std::move(events), IntegerValue(1),
1020 return absl::StrCat(
"CtEvent(x_end = ",
x_end.DebugString(),
1040bool ComputeWeightedSumOfEndMinsForOnePermutation(
1041 absl::Span<const PermutableEvent> events, IntegerValue capacity_max,
1042 IntegerValue& sum_of_ends, IntegerValue& sum_of_weighted_ends,
1043 std::vector<std::pair<IntegerValue, IntegerValue>>& profile,
1044 std::vector<std::pair<IntegerValue, IntegerValue>>& new_profile) {
1046 sum_of_weighted_ends = 0;
1055 const IntegerValue start_min =
1056 std::max(event.start_min, start_of_previous_task);
1061 while (profile[current + 1].first <= start_min ||
1062 profile[current].second < event.demand) {
1066 const IntegerValue actual_start =
1067 std::max(start_min, profile[current].first);
1068 start_of_previous_task = actual_start;
1071 if (actual_start > event.start_max)
return false;
1073 const IntegerValue actual_end = actual_start +
event.size;
1074 sum_of_ends += actual_end;
1075 sum_of_weighted_ends +=
event.weight * actual_end;
1078 if (&event == &events.back())
break;
1081 new_profile.clear();
1082 new_profile.push_back(
1083 {actual_start, profile[current].second -
event.demand});
1086 while (profile[current].first < actual_end) {
1087 new_profile.push_back(
1088 {profile[current].first, profile[current].second -
event.demand});
1092 if (profile[current].first > actual_end) {
1093 new_profile.push_back(
1094 {actual_end, new_profile.back().second +
event.demand});
1096 while (current < profile.size()) {
1097 new_profile.push_back(profile[current]);
1100 profile.swap(new_profile);
1108 IntegerValue capacity_max,
1109 IntegerValue& min_sum_of_end_mins,
1110 IntegerValue& min_sum_of_weighted_end_mins,
1111 IntegerValue unweighted_threshold,
1112 IntegerValue weighted_threshold) {
1113 int num_explored = 0;
1119 std::vector<std::pair<IntegerValue, IntegerValue>> profile;
1120 std::vector<std::pair<IntegerValue, IntegerValue>> new_profile;
1122 IntegerValue sum_of_ends(0);
1123 IntegerValue sum_of_weighted_ends(0);
1124 if (ComputeWeightedSumOfEndMinsForOnePermutation(
1125 events, capacity_max, sum_of_ends, sum_of_weighted_ends, profile,
1127 min_sum_of_end_mins = std::min(sum_of_ends, min_sum_of_end_mins);
1128 min_sum_of_weighted_end_mins =
1129 std::min(sum_of_weighted_ends, min_sum_of_weighted_end_mins);
1131 if (min_sum_of_end_mins <= unweighted_threshold &&
1132 min_sum_of_weighted_end_mins <= weighted_threshold) {
1138 }
while (std::next_permutation(events.begin(), events.end()));
1139 VLOG(3) <<
"DP: size=" << events.size() <<
", explored = " << num_explored
1140 <<
", pruned = " << num_pruned
1141 <<
", min_sum_of_end_mins = " << min_sum_of_end_mins
1142 <<
", min_sum_of_weighted_end_mins = "
1143 << min_sum_of_weighted_end_mins;
1144 return num_explored > 0;
1151 const std::string& cut_name, std::vector<CtEvent> events,
1155 std::sort(events.begin(), events.end(),
1157 return std::tie(e1.x_start_min, e1.y_size_min, e1.x_lp_end) <
1158 std::tie(e2.x_start_min, e2.y_size_min, e2.x_lp_end);
1160 std::vector<PermutableEvent> permutable_events;
1161 for (
int start = 0; start + 1 < events.size(); ++start) {
1164 events[start].x_start_min == events[start - 1].x_start_min) {
1168 const IntegerValue sequence_start_min = events[start].x_start_min;
1169 std::vector<CtEvent> residual_tasks(events.begin() + start, events.end());
1175 for (
int before = 0; before < start; ++before) {
1176 if (events[before].x_start_min + events[before].x_size_min >
1177 sequence_start_min) {
1178 residual_tasks.push_back(events[before]);
1179 residual_tasks.back().lifted =
true;
1183 std::sort(residual_tasks.begin(), residual_tasks.end(),
1185 return e1.x_lp_end < e2.x_lp_end;
1188 IntegerValue sum_of_durations(0);
1189 IntegerValue sum_of_energies(0);
1190 double sum_of_ends_lp = 0.0;
1191 double sum_of_weighted_ends_lp = 0.0;
1192 IntegerValue sum_of_demands(0);
1194 permutable_events.clear();
1196 const CtEvent&
event = residual_tasks[
i];
1197 permutable_events.emplace_back(
i, event);
1198 sum_of_ends_lp +=
event.x_lp_end;
1199 sum_of_weighted_ends_lp +=
event.x_lp_end *
ToDouble(event.y_size_min);
1200 sum_of_demands +=
event.y_size_min;
1201 sum_of_durations +=
event.x_size_min;
1202 sum_of_energies +=
event.x_size_min *
event.y_size_min;
1207 if (
i <= 1 || sum_of_demands <= capacity_max)
continue;
1211 for (
int j = 0; j <=
i; ++j) {
1215 permutable_events[j].index = j;
1218 permutable_events, capacity_max, min_sum_of_end_mins,
1219 min_sum_of_weighted_end_mins,
1221 std::floor(sum_of_ends_lp + kMinCutViolation),
1223 std::floor(sum_of_weighted_ends_lp + kMinCutViolation))) {
1227 const double unweigthed_violation =
1228 (
ToDouble(min_sum_of_end_mins) - sum_of_ends_lp) /
1230 const double weighted_violation =
1231 (
ToDouble(min_sum_of_weighted_end_mins) - sum_of_weighted_ends_lp) /
1235 if (unweigthed_violation > weighted_violation &&
1236 unweigthed_violation > kMinCutViolation) {
1239 bool is_lifted =
false;
1240 for (
int j = 0; j <=
i; ++j) {
1241 const CtEvent&
event = residual_tasks[j];
1242 is_lifted |=
event.lifted;
1243 cut.
AddTerm(event.x_end, IntegerValue(1));
1245 std::string full_name = cut_name;
1250 if (weighted_violation >= unweigthed_violation &&
1251 weighted_violation > kMinCutViolation) {
1254 bool is_lifted =
false;
1255 for (
int j = 0; j <=
i; ++j) {
1256 const CtEvent&
event = residual_tasks[j];
1257 is_lifted |=
event.lifted;
1258 cut.
AddTerm(event.x_end, event.y_size_min);
1260 std::string full_name = cut_name +
"_weighted";
1261 if (is_lifted) full_name.append(
"_lifted");
1273CtEvent TrimEventAfter(IntegerValue time,
const CtEvent& old_event) {
1274 DCHECK_GT(time, old_event.x_start_min);
1275 CtEvent
event = old_event;
1276 event.lifted =
true;
1279 event.x_start_min, event.x_start_max, event.x_end_min, event.x_end_max,
1280 event.x_size_min, event.y_size_min, event.decomposed_energy, time,
1282 event.x_size_min =
event.x_size_min +
event.x_start_min - time;
1283 event.x_start_min = time;
1284 if (event.energy_min > event.x_size_min * event.y_size_min) {
1285 event.use_energy =
true;
1287 DCHECK_GE(event.energy_min, event.x_size_min * event.y_size_min);
1293void AddEventDemandsToCapacitySubsetSum(
1295 IntegerValue capacity_max, std::vector<int64_t>& tmp_possible_demands,
1297 if (dp.CurrentMax() != capacity_max) {
1298 if (event.y_size_is_fixed) {
1299 dp.Add(event.y_size_min.value());
1300 }
else if (!event.decomposed_energy.empty()) {
1301 tmp_possible_demands.clear();
1302 for (
const auto& [literal, size, demand] : event.decomposed_energy) {
1303 if (assignment.LiteralIsFalse(literal))
continue;
1304 if (assignment.LiteralIsTrue(literal)) {
1305 tmp_possible_demands.assign({demand.value()});
1308 tmp_possible_demands.push_back(demand.value());
1310 dp.AddChoices(tmp_possible_demands);
1314 dp.Add(capacity_max.value());
1366 std::vector<CtEvent> events,
1367 IntegerValue capacity_max,
1368 bool skip_low_sizes,
Model* model,
1373 std::vector<int64_t> tmp_possible_demands;
1376 std::sort(events.begin(), events.end(),
1378 return std::tie(e1.x_start_min, e1.y_size_min, e1.x_lp_end) <
1379 std::tie(e2.x_start_min, e2.y_size_min, e2.x_lp_end);
1383 for (
int start = 0; start + 1 < events.size(); ++start) {
1386 events[start].x_start_min == events[start - 1].x_start_min) {
1390 const IntegerValue sequence_start_min = events[start].x_start_min;
1391 std::vector<CtEvent> residual_tasks(events.begin() + start, events.end());
1398 for (
int before = 0; before < start; ++before) {
1399 if (events[before].x_start_min + events[before].x_size_min >
1400 sequence_start_min) {
1401 CtEvent event = TrimEventAfter(sequence_start_min, events[before]);
1402 if (event.energy_min <= 0)
continue;
1403 residual_tasks.push_back(std::move(event));
1409 double best_efficacy = 0.01;
1410 IntegerValue best_min_contrib = 0;
1411 IntegerValue best_capacity = 0;
1414 IntegerValue sum_event_contributions = 0;
1416 IntegerValue sum_energy = 0;
1418 IntegerValue sum_square_energy = 0;
1420 double lp_contrib = 0.0;
1427 std::sort(residual_tasks.begin(), residual_tasks.end(),
1429 return e1.x_lp_end < e2.x_lp_end;
1433 for (
int i = 0;
i < residual_tasks.size(); ++
i) {
1434 const CtEvent&
event = residual_tasks[
i];
1435 DCHECK_GE(event.x_start_min, sequence_start_min);
1438 if (!
AddTo(event.energy_min, &sum_energy))
break;
1447 &sum_event_contributions)) {
1450 if (!
AddSquareTo(event.energy_min, &sum_square_energy))
break;
1452 lp_contrib +=
event.x_lp_end *
ToDouble(event.energy_min);
1453 current_start_min = std::min(current_start_min, event.x_start_min);
1456 AddEventDemandsToCapacitySubsetSum(event, assignment, capacity_max,
1457 tmp_possible_demands, dp);
1461 if (skip_low_sizes &&
i < 7)
continue;
1463 const IntegerValue reachable_capacity = dp.
CurrentMax();
1466 const IntegerValue large_rectangle_contrib =
1469 const IntegerValue mean_large_rectangle_contrib =
1470 CeilRatio(large_rectangle_contrib, reachable_capacity);
1472 IntegerValue min_contrib =
1473 CapAddI(sum_event_contributions, mean_large_rectangle_contrib);
1475 min_contrib =
CeilRatio(min_contrib, 2);
1478 if (!
AddProductTo(sum_energy, current_start_min, &min_contrib))
break;
1482 const double efficacy = (
ToDouble(min_contrib) - lp_contrib) /
1483 std::sqrt(
ToDouble(sum_square_energy));
1491 if (efficacy > best_efficacy) {
1492 best_efficacy = efficacy;
1494 best_min_contrib = min_contrib;
1495 best_capacity = reachable_capacity;
1501 if (best_end != -1) {
1503 bool is_lifted =
false;
1504 bool add_energy_to_name =
false;
1505 for (
int i = 0;
i <= best_end; ++
i) {
1506 const CtEvent&
event = residual_tasks[
i];
1507 is_lifted |=
event.lifted;
1508 add_energy_to_name |=
event.use_energy;
1509 cut.
AddTerm(event.x_end, event.energy_min);
1511 std::string full_name(cut_name);
1512 if (is_lifted) full_name.append(
"_lifted");
1513 if (add_energy_to_name) full_name.append(
"_energy");
1514 if (best_capacity < capacity_max) {
1515 full_name.append(
"_subsetsum");
1534 auto generate_cuts = [model, manager, helper](
bool mirror) {
1535 std::vector<CtEvent> events;
1536 const auto& lp_values = manager->LpValues();
1537 for (
int index = 0; index < helper->
NumTasks(); ++index) {
1538 if (!helper->
IsPresent(index))
continue;
1539 const IntegerValue size_min = helper->
SizeMin(index);
1543 event.x_end = end_expr;
1544 event.x_lp_end = end_expr.
LpValue(lp_values);
1545 event.y_size_min = IntegerValue(1);
1546 event.energy_min = size_min;
1547 events.push_back(event);
1551 const std::string mirror_str = mirror ?
"_mirror" :
"";
1553 absl::StrCat(
"NoOverlapCompletionTimeExhaustive", mirror_str), events,
1554 IntegerValue(1), model, manager);
1557 absl::StrCat(
"NoOverlapCompletionTimeQueyrane", mirror_str),
1558 std::move(events), IntegerValue(1),
1559 true, model, manager);
1562 generate_cuts(
false);
1564 generate_cuts(
true);
1582 result.
generate_cuts = [integer_trail, helper, demands_helper, capacity,
1587 auto generate_cuts = [integer_trail, model, manager, helper, demands_helper,
1588 capacity](
bool mirror) {
1589 std::vector<CtEvent> events;
1590 const auto& lp_values = manager->LpValues();
1591 for (
int index = 0; index < helper->NumTasks(); ++index) {
1592 if (!helper->IsPresent(index))
continue;
1593 if (helper->SizeMin(index) > 0 &&
1594 demands_helper->DemandMin(index) > 0) {
1596 event.x_end = helper->Ends()[index];
1597 event.x_lp_end =
event.x_end.LpValue(lp_values);
1598 event.y_size_min = demands_helper->DemandMin(index);
1599 event.energy_min = demands_helper->EnergyMin(index);
1600 event.decomposed_energy = demands_helper->DecomposedEnergies()[index];
1601 event.y_size_is_fixed = demands_helper->DemandIsFixed(index);
1602 events.push_back(event);
1606 const IntegerValue capacity_max = integer_trail->
UpperBound(capacity);
1607 const std::string mirror_str = mirror ?
"_mirror" :
"";
1609 absl::StrCat(
"CumulativeCompletionTimeExhaustive", mirror_str),
1610 events, capacity_max, model, manager);
1613 absl::StrCat(
"CumulativeCompletionTimeQueyrane", mirror_str),
1614 std::move(events), capacity_max,
1615 true, model, manager);
1617 if (!helper->SynchronizeAndSetTimeDirection(
true))
return false;
1618 generate_cuts(
false);
1619 if (!helper->SynchronizeAndSetTimeDirection(
false))
return false;
1620 generate_cuts(
true);
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
IntegerValue UpperBound(IntegerVariable i) const
IntegerValue FixedValue(IntegerVariable i) const
Checks that the variable is fixed and returns its value.
bool IsFixed(IntegerVariable i) const
Checks if the variable is fixed.
void AddQuadraticLowerBound(AffineExpression left, AffineExpression right, IntegerTrail *integer_trail, bool *is_quadratic=nullptr)
LinearExpression BuildExpression()
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff=IntegerValue(1))
void AddTerm(IntegerVariable var, IntegerValue coeff)
void AddLinearExpression(const LinearExpression &expr)
void AddConstant(IntegerValue value)
Adds the corresponding term to the current linear expression.
ABSL_MUST_USE_RESULT bool AddDecomposedProduct(absl::Span< const LiteralValueValue > product)
const util_intops::StrongVector< IntegerVariable, double > & LpValues()
To simplify CutGenerator api.
LiteralIndex Index() const
void AddChoices(absl::Span< const int64_t > choices)
int64_t CurrentMax() const
void Add(int64_t value)
Add a value to the base set for which subset sums will be taken.
void Reset(int64_t bound)
IntegerValue StartMax(int t) const
int NumTasks() const
Returns the number of task.
absl::Span< const AffineExpression > Sizes() const
bool IsPresent(int t) const
Literal PresenceLiteral(int index) const
absl::Span< const AffineExpression > Ends() const
IntegerValue SizeMin(int t) const
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
bool IsAbsent(int t) const
IntegerValue EndMin(int t) const
bool CacheAllEnergyValues()
bool EnergyIsQuadratic(int t) const
const std::vector< std::vector< LiteralValueValue > > & DecomposedEnergies() const
IntegerValue DemandMax(int t) const
const std::vector< AffineExpression > & Demands() const
IntegerValue DemandMin(int t) const
ABSL_MUST_USE_RESULT bool AddLinearizedDemand(int t, LinearConstraintBuilder *builder) const
IntegerValue EnergyMin(int t) const
void TransferToManager(LinearConstraintManager *manager)
Empty the local pool and add all its content to the manager.
void AddCut(LinearConstraint ct, absl::string_view name, const util_intops::StrongVector< IntegerVariable, double > &lp_solution)
Adds a cut to the local pool.
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
bool AddSquareTo(IntegerValue a, IntegerValue *result)
Computes result += a * a, and return false iff there is an overflow.
bool AddProductTo(IntegerValue a, IntegerValue b, IntegerValue *result)
Computes result += a * b, and return false iff there is an overflow.
CutGenerator CreateCumulativeCompletionTimeCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
void GenerateCompletionTimeCutsWithEnergy(absl::string_view cut_name, std::vector< CtEvent > events, IntegerValue capacity_max, bool skip_low_sizes, Model *model, LinearConstraintManager *manager)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
bool AddTo(IntegerValue a, IntegerValue *result)
void GenerateCutsBetweenPairOfNonOverlappingTasks(absl::string_view cut_name, const util_intops::StrongVector< IntegerVariable, double > &lp_values, std::vector< CachedIntervalData > events, IntegerValue capacity_max, Model *model, LinearConstraintManager *manager)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
const LiteralIndex kNoLiteralIndex(-1)
CutGenerator CreateCumulativePrecedenceCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
void GenerateCumulativeEnergeticCuts(absl::string_view cut_name, const util_intops::StrongVector< IntegerVariable, double > &lp_values, std::vector< EnergyEvent > events, const AffineExpression &capacity, TimeLimit *time_limit, Model *model, LinearConstraintManager *manager)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
void GenerateShortCompletionTimeCutsWithExactBound(const std::string &cut_name, std::vector< CtEvent > events, IntegerValue capacity_max, Model *model, LinearConstraintManager *manager)
CutGenerator CreateCumulativeEnergyCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, const std::optional< AffineExpression > &makespan, Model *model)
void AddIntegerVariableFromIntervals(const SchedulingConstraintHelper *helper, Model *model, std::vector< IntegerVariable > *vars, int mask)
void GenerateCumulativeEnergeticCutsWithMakespanAndFixedCapacity(absl::string_view cut_name, const util_intops::StrongVector< IntegerVariable, double > &lp_values, std::vector< EnergyEvent > events, IntegerValue capacity, AffineExpression makespan, TimeLimit *time_limit, Model *model, LinearConstraintManager *manager)
CutGenerator CreateCumulativeTimeTableCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
void AppendVariablesFromCapacityAndDemands(const AffineExpression &capacity, SchedulingDemandHelper *demands_helper, Model *model, std::vector< IntegerVariable > *vars)
CutGenerator CreateNoOverlapCompletionTimeCutGenerator(SchedulingConstraintHelper *helper, 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)
IntegerValue CapAddI(IntegerValue a, IntegerValue b)
CutGenerator CreateNoOverlapEnergyCutGenerator(SchedulingConstraintHelper *helper, const std::optional< AffineExpression > &makespan, Model *model)
CutGenerator CreateNoOverlapPrecedenceCutGenerator(SchedulingConstraintHelper *helper, Model *model)
bool AtMinOrMaxInt64I(IntegerValue t)
IntegerValue CapProdI(IntegerValue a, IntegerValue b)
Overflows and saturated arithmetic.
double ToDouble(IntegerValue value)
IntegerValue ComputeEnergyMinInWindow(IntegerValue start_min, IntegerValue start_max, IntegerValue end_min, IntegerValue end_max, IntegerValue size_min, IntegerValue demand_min, absl::Span< const LiteralValueValue > filtered_energy, IntegerValue window_start, IntegerValue window_end)
In SWIG mode, we don't want anything besides these top-level includes.
trees with all degrees equal w the current value of w
AffineExpression Negated() const
double LpValue(const util_intops::StrongVector< IntegerVariable, double > &lp_values) const
Returns the affine expression value under a given LP solution.
std::vector< LiteralValueValue > decomposed_energy
IntegerValue y_size_min
Cache of the bounds on the y direction.
IntegerValue x_start_min
Cache of the intervals bound on the x direction.
BaseEvent(int t, SchedulingConstraintHelper *x_helper)
IntegerValue energy_min
The energy min of this event.
CachedIntervalData(int t, SchedulingConstraintHelper *helper)
AffineExpression x_end
The lp value of the end of the x interval.
CtEvent(int t, SchedulingConstraintHelper *x_helper)
std::string DebugString() const
absl::AnyInvocable< bool(LinearConstraintManager *manager)> generate_cuts
std::vector< IntegerVariable > vars
bool only_run_at_level_zero
AffineExpression y_size
We need this for linearizing the energy in some cases.
bool energy_is_quadratic
True if linearized_energy is not exact and a McCormick relaxation.
LiteralIndex presence_literal_index
If set, this event is optional and its presence is controlled by this.
EnergyEvent(int t, SchedulingConstraintHelper *x_helper)
ABSL_MUST_USE_RESULT bool FillEnergyLp(AffineExpression x_size, const util_intops::StrongVector< IntegerVariable, double > &lp_values, Model *model)
double linearized_energy_lp_value
IntegerValue GetMinOverlap(IntegerValue start, IntegerValue end) const
LinearExpression linearized_energy
std::string DebugString() const