@@ -391,6 +391,28 @@ std::string DiffnCtEvent::DebugString() const {
391391// The original cut is:
392392// sum(end_min_i * duration_min_i) >=
393393// (sum(duration_min_i^2) + sum(duration_min_i)^2) / 2
394+ //
395+ // Let's build a figure where each horizontal rectangle represent a task. It
396+ // ends at the end of the task, and its height is the duration of the task.
397+ // For a given order, we pack each rectangle to the left while not overlapping,
398+ // that is one rectangle starts when the previous one ends.
399+ //
400+ // e1
401+ // -----
402+ // :\ | s1
403+ // : \| e2
404+ // -------------
405+ // :\ |
406+ // : \ | s2
407+ // : \| e3
408+ // ----------------
409+ // : \| s3
410+ // ----------------
411+ //
412+ // We can notice that the total area is independent of the order of tasks.
413+ // The first term of the rhs is the area above the diagonal.
414+ // The second term of the rhs is the area below the diagonal.
415+ //
394416// We apply the following changes (see the code for cumulative constraints):
395417// - we strengthen this cuts by noticing that if all tasks starts after S,
396418// then replacing end_min_i by (end_min_i - S) is still valid.
@@ -461,12 +483,11 @@ void GenerateNoOvelap2dCompletionTimeCuts(absl::string_view cut_name,
461483 // Best cut so far for this loop.
462484 int best_end = -1 ;
463485 double best_efficacy = 0.01 ;
464- IntegerValue best_min_contrib = 0 ;
465- IntegerValue best_capacity = 0 ;
466- IntegerValue best_y_range = 0 ;
486+ IntegerValue best_min_rhs = 0 ;
487+ bool best_use_subset_sum = false ;
467488
468489 // Used in the first term of the rhs of the equation.
469- IntegerValue sum_event_contributions = 0 ;
490+ IntegerValue sum_event_areas = 0 ;
470491 // Used in the second term of the rhs of the equation.
471492 IntegerValue sum_energy = 0 ;
472493 // For normalization.
@@ -486,8 +507,7 @@ void GenerateNoOvelap2dCompletionTimeCuts(absl::string_view cut_name,
486507 DCHECK_GE (event.x_start_min , sequence_start_min);
487508 // Make sure we do not overflow.
488509 if (!AddTo (event.energy_min , &sum_energy)) break ;
489- if (!AddProductTo (event.energy_min , event.x_size_min ,
490- &sum_event_contributions)) {
510+ if (!AddProductTo (event.energy_min , event.x_size_min , &sum_event_areas)) {
491511 break ;
492512 }
493513 if (!AddSquareTo (event.energy_min , &sum_square_energy)) break ;
@@ -505,7 +525,8 @@ void GenerateNoOvelap2dCompletionTimeCuts(absl::string_view cut_name,
505525 if (i == 0 ) {
506526 dp.Reset ((y_max_of_subset - y_min_of_subset).value ());
507527 } else {
508- if (y_max_of_subset - y_min_of_subset != dp.Bound ()) {
528+ // TODO(user): Can we increase the bound dynamically ?
529+ if (y_max_of_subset - y_min_of_subset > dp.Bound ()) {
509530 use_dp = false ;
510531 }
511532 }
@@ -522,23 +543,21 @@ void GenerateNoOvelap2dCompletionTimeCuts(absl::string_view cut_name,
522543 if (sum_of_y_size_min <= reachable_capacity) continue ;
523544
524545 // Do we have a violated cut ?
525- const IntegerValue large_rectangle_contrib =
526- CapProdI (sum_energy, sum_energy);
527- if (AtMinOrMaxInt64I (large_rectangle_contrib)) break ;
528- const IntegerValue mean_large_rectangle_contrib =
529- CeilRatio (large_rectangle_contrib, reachable_capacity);
546+ const IntegerValue square_sum_energy = CapProdI (sum_energy, sum_energy);
547+ if (AtMinOrMaxInt64I (square_sum_energy)) break ;
548+ const IntegerValue rhs_second_term =
549+ CeilRatio (square_sum_energy, reachable_capacity);
530550
531- IntegerValue min_contrib =
532- CapAddI (sum_event_contributions, mean_large_rectangle_contrib);
533- if (AtMinOrMaxInt64I (min_contrib)) break ;
534- min_contrib = CeilRatio (min_contrib, 2 );
551+ IntegerValue min_rhs = CapAddI (sum_event_areas, rhs_second_term);
552+ if (AtMinOrMaxInt64I (min_rhs)) break ;
553+ min_rhs = CeilRatio (min_rhs, 2 );
535554
536555 // shift contribution by current_start_min.
537- if (!AddProductTo (sum_energy, current_start_min, &min_contrib )) break ;
556+ if (!AddProductTo (sum_energy, current_start_min, &min_rhs )) break ;
538557
539558 // The efficacy of the cut is the normalized violation of the above
540559 // equation. We will normalize by the sqrt of the sum of squared energies.
541- const double efficacy = (ToDouble (min_contrib ) - lp_contrib) /
560+ const double efficacy = (ToDouble (min_rhs ) - lp_contrib) /
542561 std::sqrt (ToDouble (sum_square_energy));
543562
544563 // For a given start time, we only keep the best cut.
@@ -550,13 +569,13 @@ void GenerateNoOvelap2dCompletionTimeCuts(absl::string_view cut_name,
550569 if (efficacy > best_efficacy) {
551570 best_efficacy = efficacy;
552571 best_end = i;
553- best_min_contrib = min_contrib ;
554- best_capacity = reachable_capacity;
555- best_y_range = y_max_of_subset - y_min_of_subset;
572+ best_min_rhs = min_rhs ;
573+ best_use_subset_sum =
574+ reachable_capacity < y_max_of_subset - y_min_of_subset;
556575 }
557576 }
558577 if (best_end != -1 ) {
559- LinearConstraintBuilder cut (model, best_min_contrib , kMaxIntegerValue );
578+ LinearConstraintBuilder cut (model, best_min_rhs , kMaxIntegerValue );
560579 bool is_lifted = false ;
561580 bool add_energy_to_name = false ;
562581 for (int i = 0 ; i <= best_end; ++i) {
@@ -568,9 +587,7 @@ void GenerateNoOvelap2dCompletionTimeCuts(absl::string_view cut_name,
568587 std::string full_name (cut_name);
569588 if (is_lifted) full_name.append (" _lifted" );
570589 if (add_energy_to_name) full_name.append (" _energy" );
571- if (best_capacity < best_y_range) {
572- full_name.append (" _subsetsum" );
573- }
590+ if (best_use_subset_sum) full_name.append (" _subsetsum" );
574591 top_n_cuts.AddCut (cut.Build (), full_name, manager->LpValues ());
575592 }
576593 }
0 commit comments