// Copyright 2010-2021 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_DIFFN_UTIL_H_ #define OR_TOOLS_SAT_DIFFN_UTIL_H_ #include #include #include #include #include #include #include "absl/container/flat_hash_set.h" #include "absl/random/bit_gen_ref.h" #include "absl/strings/str_format.h" #include "absl/types/span.h" #include "ortools/graph/connected_components.h" #include "ortools/sat/integer.h" #include "ortools/sat/intervals.h" #include "ortools/util/strong_integers.h" namespace operations_research { namespace sat { struct Rectangle { IntegerValue x_min; IntegerValue x_max; IntegerValue y_min; IntegerValue y_max; void TakeUnionWith(const Rectangle& other) { x_min = std::min(x_min, other.x_min); y_min = std::min(y_min, other.y_min); x_max = std::max(x_max, other.x_max); y_max = std::max(y_max, other.y_max); } IntegerValue Area() const { return (x_max - x_min) * (y_max - y_min); } bool IsDisjoint(const Rectangle& other) const; std::string DebugString() const { return absl::StrFormat("rectangle(x(%i..%i), y(%i..%i))", x_min.value(), x_max.value(), y_min.value(), y_max.value()); } }; // Creates a graph when two nodes are connected iif their rectangles overlap. // Then partition into connected components. // // This method removes all singleton components. It will modify the // active_rectangle span in place. std::vector> GetOverlappingRectangleComponents( const std::vector& rectangles, absl::Span active_rectangles); // Visible for testing. The algo is in O(n^4) so shouldn't be used directly. // Returns true if there exist a bounding box with too much energy. bool BoxesAreInEnergyConflict(const std::vector& rectangles, const std::vector& energies, absl::Span boxes, Rectangle* conflict = nullptr); // Checks that there is indeed a conflict for the given bounding_box and // report it. This returns false for convenience as we usually want to return // false on a conflict. // // TODO(user): relax the bounding box dimension to have a relaxed explanation. // We can also minimize the number of required intervals. bool ReportEnergyConflict(Rectangle bounding_box, absl::Span boxes, SchedulingConstraintHelper* x, SchedulingConstraintHelper* y); // A O(n^2) algorithm to analyze all the relevant X intervals and infer a // threshold of the y size of a bounding box after which there is no point // checking for energy overload. // // Returns false on conflict, and fill the bounding box that caused the // conflict. // // If transpose is true, we analyze the relevant Y intervals instead. bool AnalyzeIntervals(bool transpose, absl::Span boxes, const std::vector& rectangles, const std::vector& rectangle_energies, IntegerValue* x_threshold, IntegerValue* y_threshold, Rectangle* conflict = nullptr); // Removes boxes with a size above the thresholds. Also randomize the order. // Because we rely on various heuristic, this allow to change the order from // one call to the next. absl::Span FilterBoxesAndRandomize( const std::vector& cached_rectangles, absl::Span boxes, IntegerValue threshold_x, IntegerValue threshold_y, absl::BitGenRef random); // Given the total energy of all rectangles (sum of energies[box]) we know that // any box with an area greater than that cannot participate in any "bounding // box" conflict. As we remove this box, the total energy decrease, so we might // remove more. This works in O(n log n). absl::Span FilterBoxesThatAreTooLarge( const std::vector& cached_rectangles, const std::vector& energies, absl::Span boxes); struct IndexedInterval { int index; IntegerValue start; IntegerValue end; bool operator==(const IndexedInterval& rhs) const { return std::tie(start, end, index) == std::tie(rhs.start, rhs.end, rhs.index); } // NOTE(user): We would like to use TUPLE_DEFINE_STRUCT, but sadly it doesn't // support //buildenv/target:non_prod. struct ComparatorByStartThenEndThenIndex { bool operator()(const IndexedInterval& a, const IndexedInterval& b) const { return std::tie(a.start, a.end, a.index) < std::tie(b.start, b.end, b.index); } }; struct ComparatorByStart { bool operator()(const IndexedInterval& a, const IndexedInterval& b) const { return a.start < b.start; } }; }; std::ostream& operator<<(std::ostream& out, const IndexedInterval& interval); // Given n fixed intervals, returns the subsets of intervals that overlap during // at least one time unit. Note that we only return "maximal" subset and filter // subset strictly included in another. // // All Intervals must have a positive size. // // The algo is in O(n log n) + O(result_size) which is usually O(n^2). void ConstructOverlappingSets(bool already_sorted, std::vector* intervals, std::vector>* result); // Given n intervals, returns the set of connected components (using the overlap // relation between 2 intervals). Components are sorted by their start, and // inside a component, the intervals are also sorted by start. // `intervals` is only sorted (by start), and not modified otherwise. void GetOverlappingIntervalComponents( std::vector* intervals, std::vector>* components); // Similar to GetOverlappingIntervalComponents(), but returns the indices of // all intervals whose removal would create one more connected component in the // interval graph. Those are sorted by start. See: // https://en.wikipedia.org/wiki/Glossary_of_graph_theory#articulation_point. std::vector GetIntervalArticulationPoints( std::vector* intervals); // This class is used by the no_overlap_2d constraint to maintain the envelope // of a set of rectangles. This envelope is not the convex hull, but the exact // polyline (aligned with the x and y axis) that contains all the rectangles // passed with the AddRectangle() call. class CapacityProfile { public: // Simple start of a rectangle. This is used to represent the residual // capacity profile. struct Rectangle { Rectangle(IntegerValue start, IntegerValue height) : start(start), height(height) {} bool operator<(const Rectangle& other) const { return start < other.start; } bool operator==(const Rectangle& other) const { return start == other.start && height == other.height; } IntegerValue start = IntegerValue(0); IntegerValue height = IntegerValue(0); }; void Clear(); // Adds a rectangle to the current shape. void AddRectangle(IntegerValue x_min, IntegerValue x_max, IntegerValue y_min, IntegerValue y_max); // Adds a mandatory profile consumption. All mandatory usages will be // subtracted from the y_max-y_min profile to build the residual capacity. void AddMandatoryConsumption(IntegerValue x_min, IntegerValue x_max, IntegerValue y_height); // Returns the profile of the function: // capacity(x) = max(y_max of rectangles overlapping x) - min(y_min of // rectangle overlapping x) - sum(y_height of mandatory rectangles // overlapping x) where a rectangle overlaps x if x_min <= x < x_max. // // Note the profile can contain negative heights in case the mandatory part // exceeds the range on the y axis. // // Note that it adds a sentinel (kMinIntegerValue, 0) at the start. It is // useful when we reverse the direction on the x axis. void BuildResidualCapacityProfile(std::vector* result); // Returns the exact area of the bounding polyline of all rectangles added. // // Note that this will redo the computation each time. IntegerValue GetBoundingArea(); private: // Type for the capacity events. enum EventType { START_RECTANGLE, END_RECTANGLE, CHANGE_MANDATORY_PROFILE }; // Individual events. struct Event { IntegerValue time; IntegerValue y_min; IntegerValue y_max; EventType type; int index; const bool operator<(const Event& other) const { return time < other.time; } }; // Element of the integer_pq heap. struct QueueElement { int Index() const { return index; } const bool operator<(const QueueElement& o) const { return value < o.value; } int index; IntegerValue value; }; static Event StartRectangleEvent(int index, IntegerValue x_min, IntegerValue y_min, IntegerValue y_max) { return {x_min, y_min, y_max, START_RECTANGLE, index}; } static Event EndRectangleEvent(int index, IntegerValue x_max) { return {x_max, kMinIntegerValue, kMinIntegerValue, END_RECTANGLE, index}; } static Event ChangeMandatoryProfileEvent(IntegerValue x, IntegerValue delta) { return {x, /*y_min=*/delta, /*y_max=*/kMinIntegerValue, CHANGE_MANDATORY_PROFILE, /*index=*/-1}; } std::vector events_; int num_rectangles_added_ = 0; }; } // namespace sat } // namespace operations_research #endif // OR_TOOLS_SAT_DIFFN_UTIL_H_