@@ -706,56 +706,109 @@ bool NonOverlappingRectanglesDisjunctivePropagator::
706706 FindBoxesThatMustOverlapAHorizontalLineAndPropagate (
707707 bool fast_propagation, SchedulingConstraintHelper* x,
708708 SchedulingConstraintHelper* y) {
709- // Note that since we only push bounds on x, we cache the value for y just
710- // once.
711- if (!y->SynchronizeAndSetTimeDirection (true )) return false ;
712-
713- // Compute relevant boxes, the one with a mandatory part of y. Because we will
709+ // When they are many fixed box that we know do not overlap, we compute
710+ // the bounding box of the others, and we can exclude all boxes outside this
711+ // region. This can help, especially for some LNS neighborhood.
712+ int num_fixed = 0 ;
713+ int num_others = 0 ;
714+ Rectangle other_bounding_box;
715+
716+ // push_back() can be slow as it might not be inlined, so we manage directly
717+ // our "boxes" in boxes_data[0 .. num_boxes], with a memory that is always big
718+ // enough.
719+ indexed_boxes_.resize (y->NumTasks ());
720+ int num_boxes = 0 ;
721+ IndexedInterval* boxes_data = indexed_boxes_.data ();
722+
723+ // Compute relevant boxes, the one with a mandatory part on y. Because we will
714724 // need to sort it this way, we consider them by increasing start max.
715- indexed_boxes_.clear ();
716725 const auto temp = y->TaskByIncreasingNegatedStartMax ();
726+ auto fixed_boxes = already_checked_fixed_boxes_.view ();
717727 for (int i = temp.size (); --i >= 0 ;) {
718728 const int box = temp[i].task_index ;
719- // Ignore absent boxes.
720- if (x->IsAbsent (box) || y->IsAbsent (box)) continue ;
721-
722- // Ignore boxes where the relevant presence literal is only on the y
723- // dimension, or if both intervals are optionals with different literals.
724- if (x->IsPresent (box) && !y->IsPresent (box)) continue ;
725- if (!x->IsPresent (box) && !y->IsPresent (box) &&
726- x->PresenceLiteral (box) != y->PresenceLiteral (box)) {
727- continue ;
729+
730+ // By definition, fixed boxes are always present.
731+ // Doing this check optimize a bit the case where we have many fixed boxes.
732+ if (!fixed_boxes[box]) {
733+ // Ignore absent boxes.
734+ if (x->IsAbsent (box) || y->IsAbsent (box)) continue ;
735+
736+ // Ignore boxes where the relevant presence literal is only on the y
737+ // dimension, or if both intervals are optionals with different literals.
738+ if (x->IsPresent (box) && !y->IsPresent (box)) continue ;
739+ if (!x->IsPresent (box) && !y->IsPresent (box) &&
740+ x->PresenceLiteral (box) != y->PresenceLiteral (box)) {
741+ continue ;
742+ }
728743 }
729744
745+ // Only consider box with a mandatory part on y.
730746 const IntegerValue start_max = -temp[i].time ;
731747 const IntegerValue end_min = y->EndMin (box);
732748 if (start_max < end_min) {
733- indexed_boxes_.push_back ({box, start_max, end_min});
749+ boxes_data[num_boxes++] = {box, start_max, end_min};
750+
751+ // Optim: If many rectangle are fixed and known not to overlap, we might
752+ // filter them out.
753+ if (fixed_boxes[box]) {
754+ ++num_fixed;
755+ } else {
756+ const bool is_fixed = x->StartIsFixed (box) && x->EndIsFixed (box) &&
757+ y->StartIsFixed (box) && y->EndIsFixed (box);
758+ if (is_fixed) {
759+ // We will "check it" below, so it will be checked next time.
760+ fixed_boxes.Set (box);
761+ }
762+
763+ const Rectangle r = {x->StartMin (box), x->EndMax (box), start_max,
764+ end_min};
765+ if (num_others == 0 ) {
766+ other_bounding_box = r;
767+ } else {
768+ other_bounding_box.GrowToInclude (r);
769+ }
770+ ++num_others;
771+ }
734772 }
735773 }
736774
737- // Less than 2 boxes, no propagation.
738- if (indexed_boxes_.size () < 2 ) return true ;
739-
740- // In ConstructOverlappingSets() we will always sort the output by
741- // x.ShiftedStartMin(t). We want to speed that up so we cache the order here.
742- if (!x->SynchronizeAndSetTimeDirection (x->CurrentTimeIsForward ())) {
743- return false ;
775+ // We remove from boxes_data all the fixed and checked box outside the
776+ // other_bounding_box.
777+ //
778+ // TODO(user): We could be smarter here, if we have just a few non-fixed
779+ // boxes, likely their mandatory y-part do not span the whole horizon, so
780+ // we could remove any fixed boxes outside these "stripes".
781+ if (num_others == 0 ) return true ;
782+ if (num_fixed > 0 ) {
783+ int new_size = 0 ;
784+ for (int i = 0 ; i < num_boxes; ++i) {
785+ const IndexedInterval& interval = boxes_data[i];
786+ const int box = interval.index ;
787+ const Rectangle r = {x->StartMin (box), x->EndMax (box), interval.start ,
788+ interval.end };
789+ if (other_bounding_box.IsDisjoint (r)) continue ;
790+ boxes_data[new_size++] = interval;
791+ }
792+ num_boxes = new_size;
744793 }
745794
746- // Optim: Abort if all rectangle can be fixed to their mandatory y + minimium
747- // x position without any overlap. Technically we might still propagate the x
748- // end in this setting, but the current code will just abort below in
749- // SplitDisjointBoxes() anyway.
795+ // Less than 2 boxes, no propagation.
796+ const auto boxes = absl::MakeSpan (boxes_data, num_boxes);
797+ if (boxes.size () < 2 ) return true ;
798+
799+ // Optim: Abort if all rectangle can be fixed to their mandatory y +
800+ // minimium x position without any overlap.
750801 //
751802 // This is guaranteed to be O(N log N) whereas the algo below is O(N ^ 2).
752- if (indexed_boxes_.size () > 100 ) {
803+ //
804+ // TODO(user): We might still propagate the x end in this setting, but the
805+ // current code will just abort below in SplitDisjointBoxes() anyway.
806+ {
753807 rectangles_.clear ();
754- rectangles_.reserve (indexed_boxes_.size ());
755- for (const auto [box, y_mandatory_start, y_mandatory_end] :
756- indexed_boxes_) {
757- // Note that we invert the x/y position here in order to be already sorted
758- // for FindOneIntersectionIfPresent()
808+ rectangles_.reserve (boxes.size ());
809+ for (const auto [box, y_mandatory_start, y_mandatory_end] : boxes) {
810+ // Note that we invert the x/y position here in order to be already
811+ // sorted for FindOneIntersectionIfPresent()
759812 rectangles_.push_back (
760813 {/* x_min=*/ y_mandatory_start, /* x_max=*/ y_mandatory_end,
761814 /* y_min=*/ x->StartMin (box), /* y_max=*/ x->EndMin (box)});
@@ -769,6 +822,8 @@ bool NonOverlappingRectanglesDisjunctivePropagator::
769822 }
770823 if (opt_pair == std::nullopt ) {
771824 return true ;
825+ } else {
826+ // TODO(user): Test if we have a conflict here.
772827 }
773828 }
774829
@@ -779,13 +834,12 @@ bool NonOverlappingRectanglesDisjunctivePropagator::
779834 order_[t] = i++;
780835 }
781836 }
782- ConstructOverlappingSets (absl::MakeSpan (indexed_boxes_),
783- &events_overlapping_boxes_, order_);
837+ ConstructOverlappingSets (boxes, &events_overlapping_boxes_, order_);
784838
785839 // Split lists of boxes into disjoint set of boxes (w.r.t. overlap).
786840 boxes_to_propagate_.clear ();
787841 reduced_overlapping_boxes_.clear ();
788- int work_done = indexed_boxes_ .size ();
842+ int work_done = boxes .size ();
789843 for (int i = 0 ; i < events_overlapping_boxes_.size (); ++i) {
790844 work_done += events_overlapping_boxes_[i].size ();
791845 SplitDisjointBoxes (*x, events_overlapping_boxes_[i], &disjoint_boxes_);
@@ -803,10 +857,11 @@ bool NonOverlappingRectanglesDisjunctivePropagator::
803857
804858 // And finally propagate.
805859 //
806- // TODO(user): Sorting of boxes seems influential on the performance. Test.
860+ // TODO(user): Sorting of boxes seems influential on the performance.
861+ // Test.
807862 for (const absl::Span<const int > boxes : boxes_to_propagate_) {
808- // The case of two boxes should be taken care of during "fast" propagation,
809- // so we can skip it here.
863+ // The case of two boxes should be taken care of during "fast"
864+ // propagation, so we can skip it here.
810865 if (!fast_propagation && boxes.size () <= 2 ) continue ;
811866
812867 x_.ClearOtherHelper ();
@@ -857,9 +912,22 @@ bool NonOverlappingRectanglesDisjunctivePropagator::
857912 return true ;
858913}
859914
915+ // Note that we optimized this function for two main use cases:
916+ // - smallish problem where we don't have more than 100 boxes.
917+ // - large problem with many 1000s boxes, but with only a small subset that is
918+ // not fixed (mainly coming from LNS).
860919bool NonOverlappingRectanglesDisjunctivePropagator::Propagate () {
861- global_x_.SetTimeDirection (true );
862- global_y_.SetTimeDirection (true );
920+ if (!global_x_.SynchronizeAndSetTimeDirection (true )) return false ;
921+ if (!global_y_.SynchronizeAndSetTimeDirection (true )) return false ;
922+
923+ // If we are "diving" we maintain the set of fixed boxes for which we know
924+ // that they are not overlapping.
925+ const bool backtrack_since_last_call = !rev_is_in_dive_;
926+ watcher_->SetUntilNextBacktrack (&rev_is_in_dive_);
927+ if (backtrack_since_last_call) {
928+ const int num_tasks = global_x_.NumTasks ();
929+ already_checked_fixed_boxes_.ClearAndResize (num_tasks);
930+ }
863931
864932 // Note that the code assumes that this was registered twice in fast and slow
865933 // mode. So we will not redo some propagation in slow mode that was already
0 commit comments