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"
56const double kMinCutViolation = 1e-4;
60 : x_start_min(x_helper->StartMin(t)),
61 x_start_max(x_helper->StartMax(t)),
62 x_end_min(x_helper->EndMin(t)),
63 x_end_max(x_helper->EndMax(t)),
64 x_size_min(x_helper->SizeMin(t)) {}
144ABSL_MUST_USE_RESULT
bool AddOneEvent(
145 const EnergyEvent& event, IntegerValue window_start,
146 IntegerValue window_end, LinearConstraintBuilder* cut,
147 bool* add_energy_to_name =
nullptr,
bool* add_quadratic_to_name =
nullptr,
148 bool* add_opt_to_name =
nullptr,
bool* add_lifted_to_name =
nullptr) {
149 DCHECK(cut !=
nullptr);
151 if (event.x_end_min <= window_start || event.x_start_max >= window_end) {
155 if (event.x_start_min >= window_start && event.x_end_max <= window_end) {
157 cut->AddLinearExpression(event.linearized_energy);
159 if (event.energy_is_quadratic && add_quadratic_to_name !=
nullptr) {
160 *add_quadratic_to_name =
true;
162 if (add_energy_to_name !=
nullptr &&
163 event.energy_min > event.x_size_min * event.y_size_min) {
164 *add_energy_to_name =
true;
166 if (!event.IsPresent() && add_opt_to_name !=
nullptr) {
167 *add_opt_to_name =
true;
170 const IntegerValue min_overlap =
171 event.GetMinOverlap(window_start, window_end);
172 if (min_overlap <= 0)
return true;
173 if (add_lifted_to_name !=
nullptr) *add_lifted_to_name =
true;
175 if (event.IsPresent()) {
176 const std::vector<LiteralValueValue>&
energy =
event.decomposed_energy;
178 cut->AddTerm(event.y_size, min_overlap);
180 const IntegerValue window_size = window_end - window_start;
181 for (
const auto [
lit, fixed_size, fixed_demand] :
energy) {
182 const IntegerValue alt_end_min =
183 std::max(event.x_end_min, event.x_start_min + fixed_size);
184 const IntegerValue alt_start_max =
185 std::min(event.x_start_max, event.x_end_max - fixed_size);
186 const IntegerValue energy_min =
188 std::min({alt_end_min - window_start, window_end - alt_start_max,
189 fixed_size, window_size});
190 if (energy_min == 0)
continue;
191 if (!cut->AddLiteralTerm(
lit, energy_min))
return false;
193 if (add_energy_to_name !=
nullptr) *add_energy_to_name =
true;
196 if (add_opt_to_name !=
nullptr) *add_opt_to_name =
true;
198 event.x_start_min, event.x_start_max, event.x_end_min,
199 event.x_end_max, event.x_size_min, event.y_size_min,
200 event.decomposed_energy, window_start, window_end);
201 if (min_energy > event.x_size_min * event.y_size_min &&
202 add_energy_to_name !=
nullptr) {
203 *add_energy_to_name =
true;
205 if (!cut->AddLiteralTerm(Literal(event.presence_literal_index),
216std::vector<int64_t> FindPossibleDemands(
const EnergyEvent& event,
217 const VariablesAssignment& assignment,
218 IntegerTrail* integer_trail) {
219 std::vector<int64_t> possible_demands;
220 if (event.decomposed_energy.empty()) {
221 if (integer_trail->IsFixed(event.y_size)) {
222 possible_demands.push_back(
223 integer_trail->FixedValue(event.y_size).value());
225 if (integer_trail->InitialVariableDomain(event.y_size.var).Size() >
229 for (
const int64_t var_value :
230 integer_trail->InitialVariableDomain(event.y_size.var).Values()) {
231 possible_demands.push_back(event.y_size.ValueAt(var_value).value());
235 for (
const auto [
lit, fixed_size, fixed_demand] : event.decomposed_energy) {
236 if (assignment.LiteralIsFalse(
lit))
continue;
237 possible_demands.push_back(fixed_demand.value());
240 return possible_demands;
246 absl::Span<const EnergyEvent> events, IntegerValue window_start,
247 IntegerValue window_end,
double available_energy_lp,
249 LinearConstraintBuilder* temp_builder) {
250 temp_builder->Clear();
251 for (
const EnergyEvent& event : events) {
252 if (!AddOneEvent(event, window_start, window_end, temp_builder)) {
256 return temp_builder->BuildExpression().LpValue(lp_values) >=
257 available_energy_lp * (1.0 + kMinCutViolation);
275 absl::string_view cut_name,
277 std::vector<EnergyEvent> events, IntegerValue capacity,
282 DCHECK(integer_trail->
IsFixed(capacity));
294 struct OverloadedTimeWindowWithMakespan {
297 IntegerValue fixed_energy_rhs;
298 bool use_makespan =
false;
299 bool use_subset_sum =
false;
302 std::vector<OverloadedTimeWindowWithMakespan> overloaded_time_windows;
307 std::vector<IntegerValue> time_points;
308 std::vector<std::vector<int64_t>> possible_demands(events.size());
309 const IntegerValue makespan_min = integer_trail->
LowerBound(makespan);
312 for (
int i = 0;
i < events.size(); ++
i) {
314 if (event.x_start_min < makespan_min) {
315 time_points.push_back(event.x_start_min);
317 if (event.x_start_max < makespan_min) {
318 time_points.push_back(event.x_start_max);
320 if (event.x_end_min < makespan_min) {
321 time_points.push_back(event.x_end_min);
323 if (event.x_end_max < makespan_min) {
324 time_points.push_back(event.x_end_max);
326 max_end_min = std::max(max_end_min, event.x_end_min);
327 max_end_max = std::max(max_end_max, event.x_end_max);
328 possible_demands[
i] = FindPossibleDemands(event, assignment, integer_trail);
330 time_points.push_back(makespan_min);
331 time_points.push_back(max_end_max);
334 const int num_time_points = time_points.size();
335 absl::flat_hash_map<IntegerValue, IntegerValue> reachable_capacity_ending_at;
338 for (
int i = 1;
i < num_time_points; ++
i) {
339 const IntegerValue window_start = time_points[
i - 1];
340 const IntegerValue window_end = time_points[
i];
341 reachable_capacity_subset_sum.
Reset(capacity.value());
342 for (
int i = 0;
i < events.size(); ++
i) {
344 if (event.x_start_min >= window_end || event.x_end_max <= window_start) {
347 if (possible_demands[
i].empty()) {
349 reachable_capacity_subset_sum.
Add(capacity.value());
351 reachable_capacity_subset_sum.
AddChoices(possible_demands[
i]);
353 if (reachable_capacity_subset_sum.
CurrentMax() == capacity.value())
break;
355 reachable_capacity_ending_at[window_end] =
359 const double capacity_lp =
ToDouble(capacity);
360 const double makespan_lp = makespan.
LpValue(lp_values);
361 const double makespan_min_lp =
ToDouble(makespan_min);
363 for (
int i = 0;
i + 1 < num_time_points; ++
i) {
365 if (events.size() > 50 &&
time_limit->LimitReached())
return;
367 const IntegerValue window_start = time_points[
i];
369 if (window_start >= max_end_min)
break;
371 IntegerValue cumulated_max_energy = 0;
372 IntegerValue cumulated_max_energy_before_makespan_min = 0;
373 bool use_subset_sum =
false;
374 bool use_subset_sum_before_makespan_min =
false;
376 for (
int j =
i + 1; j < num_time_points; ++j) {
377 const IntegerValue strip_start = time_points[j - 1];
378 const IntegerValue window_end = time_points[j];
379 const IntegerValue max_reachable_capacity_in_current_strip =
380 reachable_capacity_ending_at[window_end];
381 DCHECK_LE(max_reachable_capacity_in_current_strip, capacity);
384 if (max_reachable_capacity_in_current_strip < capacity) {
385 use_subset_sum =
true;
386 if (window_end <= makespan_min) {
387 use_subset_sum_before_makespan_min =
true;
391 const IntegerValue energy_in_strip =
392 (window_end - strip_start) * max_reachable_capacity_in_current_strip;
393 cumulated_max_energy += energy_in_strip;
394 if (window_end <= makespan_min) {
395 cumulated_max_energy_before_makespan_min += energy_in_strip;
398 if (window_start >= makespan_min) {
399 DCHECK_EQ(cumulated_max_energy_before_makespan_min, 0);
401 DCHECK_LE(cumulated_max_energy, capacity * (window_end - window_start));
402 const double max_energy_up_to_makespan_lp =
403 strip_start >= makespan_min
404 ?
ToDouble(cumulated_max_energy_before_makespan_min) +
405 (makespan_lp - makespan_min_lp) * capacity_lp
406 : std::numeric_limits<double>::infinity();
413 const bool use_makespan =
414 max_energy_up_to_makespan_lp <=
415 ToDouble(cumulated_max_energy) + kMinCutViolation;
416 const double available_energy_lp = use_makespan
417 ? max_energy_up_to_makespan_lp
419 if (CutIsEfficient(events, window_start, window_end, available_energy_lp,
420 lp_values, &temp_builder)) {
421 OverloadedTimeWindowWithMakespan
w;
422 w.start = window_start;
424 w.fixed_energy_rhs = use_makespan
425 ? cumulated_max_energy_before_makespan_min
426 : cumulated_max_energy;
427 w.use_makespan = use_makespan;
429 use_makespan ? use_subset_sum_before_makespan_min : use_subset_sum;
430 overloaded_time_windows.push_back(std::move(
w));
435 if (overloaded_time_windows.empty())
return;
437 VLOG(3) <<
"GenerateCumulativeEnergeticCutsWithMakespanAndFixedCapacity: "
438 << events.size() <<
" events, " << time_points.size()
439 <<
" time points, " << overloaded_time_windows.size()
440 <<
" overloads detected";
443 for (
const auto&
w : overloaded_time_windows) {
444 bool cut_generated =
true;
445 bool add_opt_to_name =
false;
446 bool add_lifted_to_name =
false;
447 bool add_quadratic_to_name =
false;
448 bool add_energy_to_name =
false;
451 if (
w.use_makespan) {
453 cut.
AddTerm(makespan, -capacity);
458 if (!AddOneEvent(event,
w.start,
w.end, &cut, &add_energy_to_name,
459 &add_quadratic_to_name, &add_opt_to_name,
460 &add_lifted_to_name)) {
461 cut_generated =
false;
467 std::string full_name(cut_name);
468 if (add_opt_to_name) full_name.append(
"_optional");
469 if (add_quadratic_to_name) full_name.append(
"_quadratic");
470 if (add_lifted_to_name) full_name.append(
"_lifted");
471 if (add_energy_to_name) full_name.append(
"_energy");
472 if (
w.use_makespan) full_name.append(
"_makespan");
473 if (
w.use_subset_sum) full_name.append(
"_subsetsum");
474 top_n_cuts.
AddCut(cut.
Build(), full_name, lp_values);
482 const std::string& cut_name,
486 double max_possible_energy_lp = 0.0;
488 max_possible_energy_lp +=
event.linearized_energy_lp_value;
498 struct OverloadedTimeWindow {
502 std::vector<OverloadedTimeWindow> overloaded_time_windows;
503 const double capacity_lp = capacity.
LpValue(lp_values);
507 absl::btree_set<IntegerValue> time_points_set;
510 time_points_set.insert(event.x_start_min);
511 time_points_set.insert(event.x_start_max);
512 time_points_set.insert(event.x_end_min);
513 time_points_set.insert(event.x_end_max);
514 max_end_min = std::max(max_end_min, event.x_end_min);
516 const std::vector<IntegerValue> time_points(time_points_set.begin(),
517 time_points_set.end());
518 const int num_time_points = time_points.size();
521 for (
int i = 0;
i + 1 < num_time_points; ++
i) {
523 if (events.size() > 50 &&
time_limit->LimitReached())
return;
525 const IntegerValue window_start = time_points[
i];
527 if (window_start >= max_end_min)
break;
529 for (
int j =
i + 1; j < num_time_points; ++j) {
530 const IntegerValue window_end = time_points[j];
531 const double available_energy_lp =
532 ToDouble(window_end - window_start) * capacity_lp;
533 if (available_energy_lp >= max_possible_energy_lp)
break;
534 if (CutIsEfficient(events, window_start, window_end, available_energy_lp,
535 lp_values, &temp_builder)) {
536 overloaded_time_windows.push_back({window_start, window_end});
541 if (overloaded_time_windows.empty())
return;
543 VLOG(3) <<
"GenerateCumulativeEnergeticCuts: " << events.size() <<
" events, "
544 << time_points.size() <<
" time points, "
545 << overloaded_time_windows.size() <<
" overloads detected";
548 for (
const auto& [window_start, window_end] : overloaded_time_windows) {
549 bool cut_generated =
true;
550 bool add_opt_to_name =
false;
551 bool add_lifted_to_name =
false;
552 bool add_quadratic_to_name =
false;
553 bool add_energy_to_name =
false;
557 cut.
AddTerm(capacity, window_start - window_end);
561 if (!AddOneEvent(event, window_start, window_end, &cut,
562 &add_energy_to_name, &add_quadratic_to_name,
563 &add_opt_to_name, &add_lifted_to_name)) {
564 cut_generated =
false;
570 std::string full_name = cut_name;
571 if (add_opt_to_name) full_name.append(
"_optional");
572 if (add_quadratic_to_name) full_name.append(
"_quadratic");
573 if (add_lifted_to_name) full_name.append(
"_lifted");
574 if (add_energy_to_name) full_name.append(
"_energy");
575 top_n_cuts.
AddCut(cut.
Build(), full_name, lp_values);
585 const std::optional<AffineExpression>& makespan,
Model*
model) {
591 if (makespan.has_value() && !makespan.value().IsConstant()) {
592 result.
vars.push_back(makespan.value().var);
598 result.
generate_cuts = [makespan, capacity, demands_helper, helper,
604 const auto& lp_values = manager->LpValues();
605 std::vector<EnergyEvent> events;
627 if (makespan.has_value() && integer_trail->
IsFixed(capacity)) {
629 "CumulativeEnergyM", lp_values, events,
645 const std::optional<AffineExpression>& makespan,
Model*
model) {
649 if (makespan.has_value() && !makespan.value().IsConstant()) {
650 result.
vars.push_back(makespan.value().var);
659 const auto& lp_values = manager->LpValues();
660 std::vector<EnergyEvent> events;
663 if (helper->
SizeMin(
i) == 0)
continue;
666 e.
y_size = IntegerValue(1);
677 if (makespan.has_value()) {
679 "NoOverlapEnergyM", lp_values, events,
702 struct TimeTableEvent {
706 double demand_lp = 0.0;
707 bool is_positive =
false;
708 bool use_energy =
false;
709 bool is_optional =
false;
718 std::vector<TimeTableEvent> events;
719 const auto& lp_values = manager->LpValues();
720 const double capacity_lp = capacity.
LpValue(lp_values);
733 e1.interval_index =
i;
741 e1.demand_lp = e1.demand.LpValue(lp_values);
742 e1.is_positive =
true;
746 TimeTableEvent e2 = e1;
748 e2.is_positive =
false;
750 events.push_back(e1);
751 events.push_back(e2);
757 std::sort(events.begin(), events.end(),
758 [](
const TimeTableEvent&
i,
const TimeTableEvent& j) {
759 if (i.time == j.time) {
760 if (i.is_positive == j.is_positive) {
761 return i.interval_index < j.interval_index;
763 return !i.is_positive;
765 return i.time < j.time;
768 double sum_of_demand_lp = 0.0;
769 bool positive_event_added_since_last_check =
false;
770 for (
int i = 0;
i < events.size(); ++
i) {
771 const TimeTableEvent& e = events[
i];
773 positive_event_added_since_last_check =
true;
774 sum_of_demand_lp += e.demand_lp;
778 if (positive_event_added_since_last_check) {
781 positive_event_added_since_last_check =
false;
783 if (sum_of_demand_lp >= capacity_lp + kMinCutViolation) {
785 bool use_energy =
false;
786 bool use_optional =
false;
788 cut.
AddTerm(capacity, IntegerValue(-1));
791 DCHECK(!events[
i].is_positive);
792 const IntegerValue time_point = events[
i - 1].time;
794 for (
int j = 0; j <
i; ++j) {
795 const TimeTableEvent& cut_event = events[j];
796 const int t = cut_event.interval_index;
797 DCHECK_LE(helper->
StartMax(t), time_point);
798 if (!cut_event.is_positive || helper->
EndMin(t) <= time_point) {
803 use_energy |= cut_event.use_energy;
804 use_optional |= cut_event.is_optional;
807 std::string cut_name =
"CumulativeTimeTable";
808 if (use_optional) cut_name +=
"_optional";
809 if (use_energy) cut_name +=
"_energy";
810 top_n_cuts.AddCut(cut.
Build(), cut_name, lp_values);
816 sum_of_demand_lp -= e.demand_lp;
818 top_n_cuts.TransferToManager(manager);
832 start(helper->Starts()[t]),
835 end(helper->Ends()[t]),
836 size_min(helper->SizeMin(t)) {}
850 absl::string_view cut_name,
852 std::vector<CachedIntervalData> events, IntegerValue capacity_max,
855 const int num_events = events.size();
856 if (num_events <= 1)
return;
858 std::sort(events.begin(), events.end(),
860 return e1.start_min < e2.start_min ||
861 (e1.start_min == e2.start_min && e1.end_max < e2.end_max);
873 const auto add_balas_disjunctive_cut =
874 [&](absl::string_view local_cut_name, IntegerValue start_min_1,
876 IntegerValue start_min_2, IntegerValue duration_min_2,
879 if (start_min_2 >= start_min_1 + duration_min_1 ||
880 start_min_1 >= start_min_2 + duration_min_2) {
883 const IntegerValue coeff_1 = duration_min_1 + start_min_1 - start_min_2;
884 const IntegerValue coeff_2 = duration_min_2 + start_min_2 - start_min_1;
885 const IntegerValue rhs = duration_min_1 * duration_min_2 +
886 duration_min_1 * start_min_2 +
887 duration_min_2 * start_min_1;
889 if (
ToDouble(coeff_1) * start_1.LpValue(lp_values) +
890 ToDouble(coeff_2) * start_2.LpValue(lp_values) <=
895 top_n_cuts.
AddCut(cut.
Build(), local_cut_name, lp_values);
899 for (
int i = 0;
i + 1 < num_events; ++
i) {
901 for (
int j =
i + 1; j < num_events; ++j) {
911 if (interval_1_can_precede_2 && !interval_2_can_precede_1 &&
919 absl::StrCat(cut_name,
"DetectedPrecedence"),
921 }
else if (interval_2_can_precede_1 && !interval_1_can_precede_2 &&
929 absl::StrCat(cut_name,
"DetectedPrecedence"),
932 add_balas_disjunctive_cut(absl::StrCat(cut_name,
"DisjunctionOnStart"),
935 add_balas_disjunctive_cut(absl::StrCat(cut_name,
"DisjunctionOnEnd"),
956 result.
generate_cuts = [integer_trail, helper, demands_helper, capacity,
960 std::vector<CachedIntervalData> events;
961 for (
int t = 0; t < helper->
NumTasks(); ++t) {
964 event.demand_min = demands_helper->
DemandMin(t);
965 events.push_back(event);
968 const IntegerValue capacity_max = integer_trail->
UpperBound(capacity);
970 "Cumulative", manager->LpValues(), std::move(events), capacity_max,
987 std::vector<CachedIntervalData> events;
988 for (
int t = 0; t < helper->
NumTasks(); ++t) {
991 event.demand_min = IntegerValue(1);
992 events.push_back(event);
996 "NoOverlap", manager->LpValues(), std::move(events), IntegerValue(1),
1028bool ComputeWeightedSumOfEndMinsForOnePermutation(
1029 absl::Span<const PermutableEvent> events, IntegerValue capacity_max,
1030 IntegerValue& sum_of_ends, IntegerValue& sum_of_weighted_ends,
1031 std::vector<std::pair<IntegerValue, IntegerValue>>& profile,
1032 std::vector<std::pair<IntegerValue, IntegerValue>>& new_profile) {
1034 sum_of_weighted_ends = 0;
1044 std::max(event.start_min, start_of_previous_task);
1049 while (profile[current + 1].first <=
start_min ||
1050 profile[current].second < event.demand) {
1054 const IntegerValue actual_start =
1055 std::max(
start_min, profile[current].first);
1056 start_of_previous_task = actual_start;
1059 if (actual_start > event.start_max)
return false;
1061 const IntegerValue actual_end = actual_start +
event.size;
1062 sum_of_ends += actual_end;
1063 sum_of_weighted_ends +=
event.weight * actual_end;
1066 if (&event == &events.back())
break;
1069 new_profile.clear();
1070 new_profile.push_back(
1071 {actual_start, profile[current].second -
event.demand});
1074 while (profile[current].first < actual_end) {
1075 new_profile.push_back(
1076 {profile[current].first, profile[current].second -
event.demand});
1080 if (profile[current].first > actual_end) {
1081 new_profile.push_back(
1082 {actual_end, new_profile.back().second +
event.demand});
1084 while (current < profile.size()) {
1085 new_profile.push_back(profile[current]);
1088 profile.swap(new_profile);
1096 IntegerValue capacity_max,
1097 IntegerValue& min_sum_of_end_mins,
1098 IntegerValue& min_sum_of_weighted_end_mins,
1099 IntegerValue unweighted_threshold,
1100 IntegerValue weighted_threshold) {
1101 int num_explored = 0;
1107 std::vector<std::pair<IntegerValue, IntegerValue>> profile;
1108 std::vector<std::pair<IntegerValue, IntegerValue>> new_profile;
1110 IntegerValue sum_of_ends(0);
1111 IntegerValue sum_of_weighted_ends(0);
1112 if (ComputeWeightedSumOfEndMinsForOnePermutation(
1113 events, capacity_max, sum_of_ends, sum_of_weighted_ends, profile,
1115 min_sum_of_end_mins = std::min(sum_of_ends, min_sum_of_end_mins);
1116 min_sum_of_weighted_end_mins =
1117 std::min(sum_of_weighted_ends, min_sum_of_weighted_end_mins);
1119 if (min_sum_of_end_mins <= unweighted_threshold &&
1120 min_sum_of_weighted_end_mins <= weighted_threshold) {
1126 }
while (std::next_permutation(events.begin(), events.end()));
1127 VLOG(3) <<
"DP: size=" << events.size() <<
", explored = " << num_explored
1128 <<
", pruned = " << num_pruned
1129 <<
", min_sum_of_end_mins = " << min_sum_of_end_mins
1130 <<
", min_sum_of_weighted_end_mins = "
1131 << min_sum_of_weighted_end_mins;
1132 return num_explored > 0;
1139 const std::string& cut_name, std::vector<CtEvent> events,
1143 std::sort(events.begin(), events.end(),
1145 return std::tie(e1.x_start_min, e1.y_size_min, e1.x_lp_end) <
1146 std::tie(e2.x_start_min, e2.y_size_min, e2.x_lp_end);
1148 std::vector<PermutableEvent> permutable_events;
1152 events[
start].x_start_min == events[
start - 1].x_start_min) {
1156 const IntegerValue sequence_start_min = events[
start].x_start_min;
1157 std::vector<CtEvent> residual_tasks(events.begin() +
start, events.end());
1163 for (
int before = 0; before <
start; ++before) {
1164 if (events[before].x_start_min + events[before].x_size_min >
1165 sequence_start_min) {
1166 residual_tasks.push_back(events[before]);
1167 residual_tasks.back().lifted =
true;
1171 std::sort(residual_tasks.begin(), residual_tasks.end(),
1173 return e1.x_lp_end < e2.x_lp_end;
1176 IntegerValue sum_of_durations(0);
1177 IntegerValue sum_of_energies(0);
1178 double sum_of_ends_lp = 0.0;
1179 double sum_of_weighted_ends_lp = 0.0;
1180 IntegerValue sum_of_demands(0);
1182 permutable_events.clear();
1184 const CtEvent&
event = residual_tasks[
i];
1185 permutable_events.emplace_back(
i, event);
1186 sum_of_ends_lp +=
event.x_lp_end;
1187 sum_of_weighted_ends_lp +=
event.x_lp_end *
ToDouble(event.y_size_min);
1188 sum_of_demands +=
event.y_size_min;
1189 sum_of_durations +=
event.x_size_min;
1190 sum_of_energies +=
event.x_size_min *
event.y_size_min;
1195 if (
i <= 1 || sum_of_demands <= capacity_max)
continue;
1199 for (
int j = 0; j <=
i; ++j) {
1203 permutable_events[j].index = j;
1206 permutable_events, capacity_max, min_sum_of_end_mins,
1207 min_sum_of_weighted_end_mins,
1209 std::floor(sum_of_ends_lp + kMinCutViolation),
1211 std::floor(sum_of_weighted_ends_lp + kMinCutViolation))) {
1215 const double unweigthed_violation =
1216 (
ToDouble(min_sum_of_end_mins) - sum_of_ends_lp) /
1218 const double weighted_violation =
1219 (
ToDouble(min_sum_of_weighted_end_mins) - sum_of_weighted_ends_lp) /
1223 if (unweigthed_violation > weighted_violation &&
1224 unweigthed_violation > kMinCutViolation) {
1227 bool is_lifted =
false;
1228 for (
int j = 0; j <=
i; ++j) {
1229 const CtEvent&
event = residual_tasks[j];
1230 is_lifted |=
event.lifted;
1231 cut.
AddTerm(event.x_end, IntegerValue(1));
1233 std::string full_name = cut_name;
1238 if (weighted_violation >= unweigthed_violation &&
1239 weighted_violation > kMinCutViolation) {
1242 bool is_lifted =
false;
1243 for (
int j = 0; j <=
i; ++j) {
1244 const CtEvent&
event = residual_tasks[j];
1245 is_lifted |=
event.lifted;
1246 cut.
AddTerm(event.x_end, event.y_size_min);
1248 std::string full_name = cut_name +
"_weighted";
1249 if (is_lifted) full_name.append(
"_lifted");
1272 std::vector<CtEvent> events,
1273 IntegerValue capacity_max,
1279 std::sort(events.begin(), events.end(),
1281 return std::tie(e1.x_start_min, e1.y_size_min, e1.x_lp_end) <
1282 std::tie(e2.x_start_min, e2.y_size_min, e2.x_lp_end);
1287 events[
start].x_start_min == events[
start - 1].x_start_min) {
1291 const IntegerValue sequence_start_min = events[
start].x_start_min;
1292 std::vector<CtEvent> residual_tasks(events.begin() +
start, events.end());
1301 for (
int before = 0; before <
start; ++before) {
1302 if (events[before].x_start_min + events[before].x_size_min >
1303 sequence_start_min) {
1305 CtEvent event = events[before];
1306 event.lifted =
true;
1308 event.x_start_min, event.x_start_max, event.x_end_min,
1309 event.x_end_max, event.x_size_min, event.y_size_min,
1310 event.decomposed_energy, sequence_start_min, event.x_end_max);
1312 event.x_size_min +
event.x_start_min - sequence_start_min;
1313 event.x_start_min = sequence_start_min;
1314 if (event.energy_min > event.x_size_min * event.y_size_min) {
1315 event.use_energy =
true;
1317 DCHECK_GE(event.energy_min, event.x_size_min * event.y_size_min);
1318 if (event.energy_min <= 0)
continue;
1319 residual_tasks.push_back(event);
1323 std::sort(residual_tasks.begin(), residual_tasks.end(),
1325 return e1.x_lp_end < e2.x_lp_end;
1329 double best_efficacy = 0.01;
1330 IntegerValue best_min_contrib(0);
1331 IntegerValue sum_duration(0);
1332 IntegerValue sum_square_duration(0);
1333 IntegerValue best_capacity(0);
1334 double unscaled_lp_contrib = 0.0;
1338 std::vector<int64_t> possible_demands;
1339 for (
int i = 0;
i < residual_tasks.size(); ++
i) {
1340 const CtEvent&
event = residual_tasks[
i];
1341 DCHECK_GE(event.x_start_min, sequence_start_min);
1342 const IntegerValue
energy =
event.energy_min;
1347 current_start_min = std::min(current_start_min, event.x_start_min);
1350 if (event.y_size_is_fixed) {
1351 dp.
Add(event.y_size_min.value());
1352 }
else if (!event.decomposed_energy.empty()) {
1353 possible_demands.clear();
1356 possible_demands.push_back(
demand.value());
1360 dp.
Add(capacity_max.value());
1366 if (skip_low_sizes &&
i < 7)
continue;
1372 const IntegerValue reachable_capacity = dp.
CurrentMax();
1373 IntegerValue min_contrib = 0;
1374 if (!
AddProductTo(sum_duration, sum_duration, &min_contrib))
break;
1375 if (!
AddTo(sum_square_duration, &min_contrib))
break;
1376 min_contrib = min_contrib / 2;
1378 const IntegerValue intermediate =
1379 CapProdI(sum_duration, reachable_capacity);
1381 const IntegerValue offset =
CapProdI(current_start_min, intermediate);
1383 if (!
AddTo(offset, &min_contrib))
break;
1387 const double efficacy =
1389 unscaled_lp_contrib) /
1390 std::sqrt(
ToDouble(sum_square_duration));
1393 if (efficacy > best_efficacy) {
1394 best_efficacy = efficacy;
1396 best_min_contrib = min_contrib;
1397 best_capacity = reachable_capacity;
1400 if (best_end != -1) {
1402 bool is_lifted =
false;
1403 bool add_energy_to_name =
false;
1404 for (
int i = 0;
i <= best_end; ++
i) {
1405 const CtEvent&
event = residual_tasks[
i];
1406 is_lifted |=
event.lifted;
1407 add_energy_to_name |=
event.use_energy;
1408 cut.
AddTerm(event.x_end, event.energy_min * best_capacity);
1410 std::string full_name(cut_name);
1411 if (is_lifted) full_name.append(
"_lifted");
1412 if (add_energy_to_name) full_name.append(
"_energy");
1413 if (best_capacity < capacity_max) {
1414 full_name.append(
"_subsetsum");
1415 VLOG(2) << full_name <<
": capacity = " << best_capacity <<
"/"
1434 auto generate_cuts = [
model, manager, helper](
bool mirror) {
1435 std::vector<CtEvent> events;
1436 const auto& lp_values = manager->LpValues();
1443 event.x_end = end_expr;
1444 event.x_lp_end = end_expr.
LpValue(lp_values);
1445 event.y_size_min = IntegerValue(1);
1446 event.energy_min = size_min;
1447 events.push_back(event);
1451 const std::string mirror_str = mirror ?
"Mirror" :
"";
1453 absl::StrCat(
"NoOverlapCompletionTimeExhaustive", mirror_str), events,
1454 IntegerValue(1),
model, manager);
1457 absl::StrCat(
"NoOverlapCompletionTimeQueyrane", mirror_str),
1458 std::move(events), IntegerValue(1),
1459 true,
model, manager);
1462 generate_cuts(
false);
1464 generate_cuts(
true);
1481 result.
generate_cuts = [integer_trail, helper, demands_helper, capacity,
1486 auto generate_cuts = [integer_trail,
model, manager, helper, demands_helper,
1487 capacity](
bool mirror) {
1488 std::vector<CtEvent> events;
1489 const auto& lp_values = manager->LpValues();
1491 if (!helper->IsPresent(
index))
continue;
1492 if (helper->SizeMin(
index) > 0 &&
1493 demands_helper->DemandMin(
index) > 0) {
1495 event.x_end = helper->Ends()[
index];
1496 event.x_lp_end =
event.x_end.LpValue(lp_values);
1497 event.y_size_min = demands_helper->DemandMin(
index);
1498 event.energy_min = demands_helper->EnergyMin(
index);
1499 event.decomposed_energy = demands_helper->DecomposedEnergies()[
index];
1500 event.y_size_is_fixed = demands_helper->DemandIsFixed(
index);
1501 events.push_back(event);
1505 const IntegerValue capacity_max = integer_trail->
UpperBound(capacity);
1506 const std::string mirror_str = mirror ?
"Mirror" :
"";
1508 absl::StrCat(
"CumulativeCompletionTimeExhaustive", mirror_str),
1509 events, capacity_max,
model, manager);
1512 absl::StrCat(
"CumulativeCompletionTimeQueyrane", mirror_str),
1513 std::move(events), capacity_max,
1514 true,
model, manager);
1516 if (!helper->SynchronizeAndSetTimeDirection(
true))
return false;
1517 generate_cuts(
false);
1518 if (!helper->SynchronizeAndSetTimeDirection(
false))
return false;
1519 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 EnergyIsQuadratic(int t) const
const std::vector< std::vector< LiteralValueValue > > & DecomposedEnergies() const
IntegerValue DemandMax(int t) const
void CacheAllEnergyValues()
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.
bool LiteralIsFalse(Literal literal) const
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
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)
const LiteralIndex kNoLiteralIndex(-1)
CutGenerator CreateCumulativePrecedenceCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
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 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)
CutGenerator CreateNoOverlapEnergyCutGenerator(SchedulingConstraintHelper *helper, const std::optional< AffineExpression > &makespan, Model *model)
CutGenerator CreateNoOverlapPrecedenceCutGenerator(SchedulingConstraintHelper *helper, Model *model)
void AddIntegerVariableFromIntervals(SchedulingConstraintHelper *helper, Model *model, std::vector< IntegerVariable > *vars)
Cuts helpers.
void GenerateCumulativeEnergeticCuts(const std::string &cut_name, const util_intops::StrongVector< IntegerVariable, double > &lp_values, std::vector< EnergyEvent > events, const AffineExpression &capacity, TimeLimit *time_limit, Model *model, LinearConstraintManager *manager)
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
std::optional< int64_t > end
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::string DebugString() const
Internal methods and data structures, useful for testing.
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.
std::string DebugString() const
std::vector< IntegerVariable > vars
bool only_run_at_level_zero
std::function< bool(LinearConstraintManager *manager)> generate_cuts
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
double LpValue(const util_intops::StrongVector< IntegerVariable, double > &lp_values) const
Return[s] the evaluation of the linear expression.