// Copyright 2010-2018 Google LLC // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef OR_TOOLS_SAT_DISJUNCTIVE_H_ #define OR_TOOLS_SAT_DISJUNCTIVE_H_ #include #include #include #include "ortools/base/int_type.h" #include "ortools/base/macros.h" #include "ortools/sat/integer.h" #include "ortools/sat/intervals.h" #include "ortools/sat/model.h" #include "ortools/sat/precedences.h" #include "ortools/sat/sat_base.h" #include "ortools/sat/theta_tree.h" namespace operations_research { namespace sat { // Enforces a disjunctive (or no overlap) constraint on the given interval // variables. The intervals are interpreted as [start, end) and the constraint // enforces that no time point belongs to two intervals. // // TODO(user): This is not completely true for empty intervals (start == end). // Make sure such intervals are ignored by the constraint. std::function Disjunctive( const std::vector& vars); // Creates Boolean variables for all the possible precedences of the form (task // i is before task j) and forces that, for each couple of task (i,j), either i // is before j or j is before i. Do not create any other propagators. std::function DisjunctiveWithBooleanPrecedencesOnly( const std::vector& vars); // Same as Disjunctive() + DisjunctiveWithBooleanPrecedencesOnly(). std::function DisjunctiveWithBooleanPrecedences( const std::vector& vars); // Helper class to compute the end-min of a set of tasks given their start-min // and duration-min. In Petr Vilim's PhD "Global Constraints in Scheduling", // this corresponds to his Theta-tree except that we use a O(n) implementation // for most of the function here, not a O(log(n)) one. class TaskSet { public: explicit TaskSet(int num_tasks) { sorted_tasks_.reserve(num_tasks); } struct Entry { int task; IntegerValue start_min; IntegerValue duration_min; // Note that the tie-breaking is not important here. bool operator<(Entry other) const { return start_min < other.start_min; } }; // Insertion and modification. These leave sorted_tasks_ sorted. void Clear() { sorted_tasks_.clear(); optimized_restart_ = 0; } void AddEntry(const Entry& e); void RemoveEntryWithIndex(int index); // Same as AddEntry({t, helper->ShiftedStartMin(t), helper->DurationMin(t)}). // This is a minor optimization to not call DurationMin(t) twice. void AddShiftedStartMinEntry(const SchedulingConstraintHelper& helper, int t); // Advanced usage, if the entry is present, this assumes that its start_min is // >= the end min without it, and update the datastructure accordingly. void NotifyEntryIsNowLastIfPresent(const Entry& e); // Advanced usage. Instead of calling many AddEntry(), it is more efficient to // call AddUnsortedEntry() instead, but then Sort() MUST be called just after // the insertions. Nothing is checked here, so it is up to the client to do // that properly. void AddUnsortedEntry(const Entry& e) { sorted_tasks_.push_back(e); } void Sort() { std::sort(sorted_tasks_.begin(), sorted_tasks_.end()); } // Returns the end-min for the task in the set. The time profile of the tasks // packed to the left will always be a set of contiguous tasks separated by // empty space: // // [Bunch of tasks] ... [Bunch of tasks] ... [critical tasks]. // // We call "critical tasks" the last group. These tasks will be solely // responsible for for the end-min of the whole set. The returned // critical_index will be the index of the first critical task in // SortedTasks(). // // A reason for the min end is: // - The duration-min of all the critical tasks. // - The fact that all critical tasks have a start-min greater or equal to the // first of them, that is SortedTasks()[critical_index].start_min. // // It is possible to behave like if one task was not in the set by setting // task_to_ignore to the id of this task. This returns 0 if the set is empty // in which case critical_index will be left unchanged. IntegerValue ComputeEndMin(int task_to_ignore, int* critical_index) const; IntegerValue ComputeEndMin() const; // Warning, this is only valid if ComputeEndMin() was just called. It is the // same index as if one called ComputeEndMin(-1, &critical_index), but saves // another unneeded loop. int GetCriticalIndex() const { return optimized_restart_; } const std::vector& SortedTasks() const { return sorted_tasks_; } private: std::vector sorted_tasks_; mutable int optimized_restart_ = 0; }; // ============================================================================ // Below are many of the known propagation techniques for the disjunctive, each // implemented in only one time direction and in its own propagator class. The // Disjunctive() model function above will instantiate the used ones (according // to the solver parameters) in both time directions. // // See Petr Vilim PhD "Global Constraints in Scheduling" for a description of // some of the algorithm. // ============================================================================ class DisjunctiveOverloadChecker : public PropagatorInterface { public: explicit DisjunctiveOverloadChecker(SchedulingConstraintHelper* helper) : helper_(helper) { // Resize this once and for all. task_to_event_.resize(helper_->NumTasks()); } bool Propagate() final; int RegisterWith(GenericLiteralWatcher* watcher); private: bool PropagateSubwindow(IntegerValue global_window_end); SchedulingConstraintHelper* helper_; std::vector window_; std::vector task_by_increasing_end_max_; ThetaLambdaTree theta_tree_; std::vector task_to_event_; }; class DisjunctiveDetectablePrecedences : public PropagatorInterface { public: DisjunctiveDetectablePrecedences(bool time_direction, SchedulingConstraintHelper* helper) : time_direction_(time_direction), helper_(helper), task_set_(helper->NumTasks()) {} bool Propagate() final; int RegisterWith(GenericLiteralWatcher* watcher); private: bool PropagateSubwindow(); std::vector task_by_increasing_end_min_; std::vector task_by_increasing_start_max_; std::vector processed_; std::vector to_propagate_; const bool time_direction_; SchedulingConstraintHelper* helper_; TaskSet task_set_; }; // Singleton model class wich is just a SchedulingConstraintHelper will all // the intervals. class AllIntervalsHelper : public SchedulingConstraintHelper { public: explicit AllIntervalsHelper(Model* model) : SchedulingConstraintHelper( model->GetOrCreate()->AllIntervals(), model) {} }; // This propagates the same things as DisjunctiveDetectablePrecedences, except // that it only sort the full set of intervals once and then work on a combined // set of disjunctives. template class CombinedDisjunctive : public PropagatorInterface { public: explicit CombinedDisjunctive(Model* model); // After creation, this must be called for all the disjunctive constraints // in the model. void AddNoOverlap(const std::vector& var); bool Propagate() final; private: AllIntervalsHelper* helper_; std::vector> task_to_disjunctives_; std::vector task_is_added_; std::vector task_sets_; std::vector end_mins_; }; class DisjunctiveNotLast : public PropagatorInterface { public: DisjunctiveNotLast(bool time_direction, SchedulingConstraintHelper* helper) : time_direction_(time_direction), helper_(helper), task_set_(helper->NumTasks()) {} bool Propagate() final; int RegisterWith(GenericLiteralWatcher* watcher); private: bool PropagateSubwindow(); std::vector start_min_window_; std::vector start_max_window_; const bool time_direction_; SchedulingConstraintHelper* helper_; TaskSet task_set_; }; class DisjunctiveEdgeFinding : public PropagatorInterface { public: DisjunctiveEdgeFinding(bool time_direction, SchedulingConstraintHelper* helper) : time_direction_(time_direction), helper_(helper) {} bool Propagate() final; int RegisterWith(GenericLiteralWatcher* watcher); private: bool PropagateSubwindow(IntegerValue window_end_min); const bool time_direction_; SchedulingConstraintHelper* helper_; // This only contains non-gray tasks. std::vector task_by_increasing_end_max_; // All these member are indexed in the same way. std::vector window_; ThetaLambdaTree theta_tree_; std::vector event_size_; // Task indexed. std::vector non_gray_task_to_event_; std::vector is_gray_; }; // Exploits the precedences relations of the form "this set of disjoint // IntervalVariables must be performed before a given IntegerVariable". The // relations are computed with PrecedencesPropagator::ComputePrecedences(). class DisjunctivePrecedences : public PropagatorInterface { public: DisjunctivePrecedences(bool time_direction, SchedulingConstraintHelper* helper, IntegerTrail* integer_trail, PrecedencesPropagator* precedences) : time_direction_(time_direction), helper_(helper), integer_trail_(integer_trail), precedences_(precedences), task_set_(helper->NumTasks()), task_to_arc_index_(helper->NumTasks()) {} bool Propagate() final; int RegisterWith(GenericLiteralWatcher* watcher); private: bool PropagateSubwindow(); const bool time_direction_; SchedulingConstraintHelper* helper_; IntegerTrail* integer_trail_; PrecedencesPropagator* precedences_; std::vector window_; std::vector index_to_end_vars_; TaskSet task_set_; std::vector task_to_arc_index_; std::vector before_; }; // This is an optimization for the case when we have a big number of such // pairwise constraints. This should be roughtly equivalent to what the general // disjunctive case is doing, but it dealt with variable size better and has a // lot less overhead. class DisjunctiveWithTwoItems : public PropagatorInterface { public: explicit DisjunctiveWithTwoItems(SchedulingConstraintHelper* helper) : helper_(helper) {} bool Propagate() final; int RegisterWith(GenericLiteralWatcher* watcher); private: SchedulingConstraintHelper* helper_; }; } // namespace sat } // namespace operations_research #endif // OR_TOOLS_SAT_DISJUNCTIVE_H_