// Copyright 2010-2025 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. #include "ortools/sat/diffn_util.h" #include #include #include #include #include #include #include #include #include #include #include #include "absl/algorithm/container.h" #include "absl/container/flat_hash_map.h" #include "absl/log/check.h" #include "absl/random/bit_gen_ref.h" #include "absl/random/distributions.h" #include "absl/random/random.h" #include "absl/strings/str_join.h" #include "absl/types/span.h" #include "benchmark/benchmark.h" #include "gtest/gtest.h" #include "ortools/base/gmock.h" #include "ortools/base/logging.h" #include "ortools/graph/connected_components.h" #include "ortools/graph/strongly_connected_components.h" #include "ortools/sat/2d_orthogonal_packing_testing.h" #include "ortools/sat/integer_base.h" #include "ortools/sat/util.h" #include "ortools/util/saturated_arithmetic.h" #include "ortools/util/strong_integers.h" namespace operations_research { namespace sat { namespace { using ::testing::ElementsAre; using ::testing::ElementsAreArray; using ::testing::UnorderedElementsAre; using ::testing::UnorderedElementsAreArray; TEST(CenterToCenterDistanceTest, BasicTest) { Rectangle a, b; a.x_min = 0; a.x_max = 2; a.y_min = 0; a.y_max = 2; b.x_min = 0; b.x_max = 10; b.y_min = 0; b.y_max = 10; // This is just the distance between (1,1) and (5,5) which should be sqrt(32). EXPECT_EQ(CenterToCenterL2Distance(a, b), std::sqrt(32)); // Now this is 4. EXPECT_EQ(CenterToCenterLInfinityDistance(a, b), 4.0); } TEST(GetOverlappingRectangleComponentsTest, Disconnected) { EXPECT_TRUE(GetOverlappingRectangleComponents({}).empty()); IntegerValue zero(0); IntegerValue two(2); IntegerValue four(4); EXPECT_THAT( GetOverlappingRectangleComponents( {{zero, two, zero, two}, {two, four, two, four}}) .AsVectorOfSpan(), UnorderedElementsAre(UnorderedElementsAre(0), UnorderedElementsAre(1))); EXPECT_THAT( GetOverlappingRectangleComponents( {{zero, two, zero, two}, {two, four, zero, two}}) .AsVectorOfSpan(), UnorderedElementsAre(UnorderedElementsAre(0), UnorderedElementsAre(1))); EXPECT_THAT( GetOverlappingRectangleComponents( {{zero, two, zero, two}, {zero, two, two, four}}) .AsVectorOfSpan(), UnorderedElementsAre(UnorderedElementsAre(0), UnorderedElementsAre(1))); } TEST(GetOverlappingRectangleComponentsTest, ComponentAndActive) { EXPECT_TRUE(GetOverlappingRectangleComponents({}).empty()); IntegerValue zero(0); IntegerValue one(1); IntegerValue two(2); IntegerValue three(3); IntegerValue four(4); EXPECT_THAT(GetOverlappingRectangleComponents({{zero, two, zero, two}, {zero, two, one, three}, {zero, two, two, four}}) .AsVectorOfSpan(), UnorderedElementsAre(UnorderedElementsAre(0, 1, 2))); EXPECT_THAT( GetOverlappingRectangleComponents( {{zero, two, zero, two}, {zero, two, two, four}}) .AsVectorOfSpan(), UnorderedElementsAre(UnorderedElementsAre(0), UnorderedElementsAre(1))); } TEST(AnalyzeIntervalsTest, Random) { // Generate a random set of intervals until the first conflict. We are in n^5! absl::BitGen random; const int64_t size = 20; std::vector rectangles; std::vector energies; std::vector boxes; for (int i = 0; i < 40; ++i) { Rectangle box; box.x_min = IntegerValue(absl::Uniform(random, 0, size)); box.x_max = IntegerValue(absl::Uniform(random, box.x_min.value() + 1, size + 1)); box.y_min = IntegerValue(absl::Uniform(random, 0, size)); box.y_max = IntegerValue(absl::Uniform(random, box.y_min.value() + 1, size + 1)); rectangles.push_back(box); boxes.push_back(i); energies.push_back(IntegerValue(absl::Uniform( random, 1, (box.x_max - box.x_min + 1).value())) * IntegerValue(absl::Uniform( random, 1, (box.y_max - box.y_min + 1).value()))); LOG(INFO) << i << " " << box << " energy:" << energies.back(); Rectangle conflict; if (!BoxesAreInEnergyConflict(rectangles, energies, boxes, &conflict)) { continue; } LOG(INFO) << "Conflict! " << conflict; // Make sure whatever filter we do, we do not remove the conflict. absl::Span s = absl::MakeSpan(boxes); IntegerValue threshold_x = kMaxIntegerValue; IntegerValue threshold_y = kMaxIntegerValue; for (int i = 0; i < 4; ++i) { if (!AnalyzeIntervals(/*transpose=*/i % 2 == 1, s, rectangles, energies, &threshold_x, &threshold_y)) { LOG(INFO) << "Detected by analyse."; return; } s = FilterBoxesAndRandomize(rectangles, s, threshold_x, threshold_y, random); LOG(INFO) << "Filtered size: " << s.size() << " x<=" << threshold_x << " y<=" << threshold_y; ASSERT_TRUE(BoxesAreInEnergyConflict(rectangles, energies, s)); } break; } } TEST(FilterBoxesThatAreTooLargeTest, Empty) { std::vector r; std::vector energies; std::vector boxes; EXPECT_TRUE( FilterBoxesThatAreTooLarge(r, energies, absl::MakeSpan(boxes)).empty()); } TEST(FilterBoxesThatAreTooLargeTest, BasicTest) { int num_boxes(3); std::vector r(num_boxes); std::vector energies(num_boxes, IntegerValue(25)); std::vector boxes{0, 1, 2}; r[0] = {IntegerValue(0), IntegerValue(5), IntegerValue(0), IntegerValue(5)}; r[1] = {IntegerValue(0), IntegerValue(10), IntegerValue(0), IntegerValue(10)}; r[2] = {IntegerValue(0), IntegerValue(6), IntegerValue(0), IntegerValue(6)}; EXPECT_THAT(FilterBoxesThatAreTooLarge(r, energies, absl::MakeSpan(boxes)), ElementsAre(0, 2)); } TEST(ConstructOverlappingSetsTest, BasicTest) { // --------------------0 // --------1 --------2 // ------------3 // ------4 std::vector intervals{{0, IntegerValue(0), IntegerValue(10)}, {1, IntegerValue(0), IntegerValue(4)}, {2, IntegerValue(6), IntegerValue(10)}, {3, IntegerValue(2), IntegerValue(8)}, {4, IntegerValue(3), IntegerValue(6)}}; absl::c_sort(intervals, IndexedInterval::ComparatorByStart()); // Note that the order is deterministic, but not sorted. CompactVectorVector result; result.Add({0, 1, 2}); // To be sure we clear. ConstructOverlappingSets(absl::MakeSpan(intervals), &result); EXPECT_THAT(result.AsVectorOfSpan(), ElementsAre(UnorderedElementsAre(0, 1, 3, 4), UnorderedElementsAre(3, 0, 2))); } TEST(ConstructOverlappingSetsTest, OneSet) { std::vector intervals{ {0, IntegerValue(0), IntegerValue(10)}, {1, IntegerValue(1), IntegerValue(10)}, {2, IntegerValue(2), IntegerValue(10)}, {3, IntegerValue(3), IntegerValue(10)}, {4, IntegerValue(4), IntegerValue(10)}}; absl::c_sort(intervals, IndexedInterval::ComparatorByStart()); CompactVectorVector result; result.Add({0, 1, 2}); // To be sure we clear. ConstructOverlappingSets(absl::MakeSpan(intervals), &result); EXPECT_THAT(result.AsVectorOfSpan(), ElementsAre(ElementsAre(0, 1, 2, 3, 4))); } TEST(GetOverlappingIntervalComponentsTest, BasicTest) { std::vector> components{{3}}; // To be sure we clear. std::vector intervals{{0, IntegerValue(0), IntegerValue(3)}, {1, IntegerValue(2), IntegerValue(4)}, {2, IntegerValue(4), IntegerValue(7)}, {3, IntegerValue(8), IntegerValue(10)}, {4, IntegerValue(5), IntegerValue(9)}}; GetOverlappingIntervalComponents(&intervals, &components); EXPECT_THAT(components, ElementsAre(ElementsAre(0, 1), ElementsAre(2, 4, 3))); } TEST(GetOverlappingIntervalComponentsAndArticulationPointsTest, WithWeirdIndicesAndSomeCornerCases) { // Here are our intervals: 2======5 7====9 // They are indexed from top to 0===2 4=====7 8======11 // bottom, from left to right, 1===3 5=6 7=8 // starting at 10. std::vector intervals{ {10, IntegerValue(2), IntegerValue(5)}, {11, IntegerValue(7), IntegerValue(9)}, {12, IntegerValue(0), IntegerValue(2)}, {13, IntegerValue(4), IntegerValue(7)}, {14, IntegerValue(8), IntegerValue(11)}, {15, IntegerValue(1), IntegerValue(3)}, {16, IntegerValue(5), IntegerValue(6)}, {17, IntegerValue(7), IntegerValue(8)}, }; std::vector> components; GetOverlappingIntervalComponents(&intervals, &components); EXPECT_THAT(components, ElementsAre(ElementsAre(12, 15, 10, 13, 16), ElementsAre(17, 11, 14))); EXPECT_THAT(GetIntervalArticulationPoints(&intervals), ElementsAre(15, 10, 13, 11)); } std::vector GenerateRandomIntervalVector( absl::BitGenRef random, int num_intervals) { std::vector intervals; intervals.reserve(num_intervals); const int64_t interval_domain = absl::LogUniform(random, 1, std::numeric_limits::max()); const int64_t max_interval_length = absl::Uniform( random, std::max(1, interval_domain / (2 * num_intervals + 1)), interval_domain); for (int i = 0; i < num_intervals; ++i) { const int64_t start = absl::Uniform(random, 0, interval_domain); const int64_t max_length = std::min(interval_domain - start, max_interval_length); const int64_t end = start + absl::Uniform(absl::IntervalClosed, random, 1, max_length); intervals.push_back( IndexedInterval{i, IntegerValue(start), IntegerValue(end)}); } return intervals; } std::vector> GetOverlappingIntervalComponentsBruteForce( absl::Span intervals) { // Build the adjacency list. std::vector> adj(intervals.size()); for (int i = 1; i < intervals.size(); ++i) { for (int j = 0; j < i; ++j) { if (std::max(intervals[i].start, intervals[j].start) < std::min(intervals[i].end, intervals[j].end)) { adj[i].push_back(j); adj[j].push_back(i); } } } std::vector component_indices = util::GetConnectedComponents(intervals.size(), adj); if (component_indices.empty()) return {}; // Transform that into the expected output: a vector of components. std::vector> components( *absl::c_max_element(component_indices) + 1); for (int i = 0; i < intervals.size(); ++i) { components[component_indices[i]].push_back(i); } // Sort the components by start, like GetOverlappingIntervalComponents(). absl::c_sort(components, [intervals](absl::Span c1, absl::Span c2) { CHECK(!c1.empty() && !c2.empty()); return intervals[c1[0]].start < intervals[c2[0]].start; }); // Inside each component, the intervals should be sorted, too. // Moreover, we need to convert our indices to IntervalIndex.index. for (std::vector& component : components) { absl::c_sort(component, [intervals](int i, int j) { return IndexedInterval::ComparatorByStartThenEndThenIndex()(intervals[i], intervals[j]); }); for (int& index : component) index = intervals[index].index; } return components; } TEST(GetOverlappingIntervalComponentsTest, RandomizedStressTest) { // Test duration as of 2021-06: .6s in fastbuild, .3s in opt. constexpr int kNumTests = 10000; absl::BitGen random; for (int test = 0; test < kNumTests; ++test) { const int num_intervals = absl::Uniform(random, 0, 16); std::vector intervals = GenerateRandomIntervalVector(random, num_intervals); const std::vector intervals_copy = intervals; std::vector> components; GetOverlappingIntervalComponents(&intervals, &components); ASSERT_THAT( components, ElementsAreArray(GetOverlappingIntervalComponentsBruteForce(intervals))) << test << " " << absl::StrJoin(intervals_copy, ","); // Also verify that the function only altered the order of "intervals". EXPECT_THAT(intervals, UnorderedElementsAreArray(intervals_copy)); ASSERT_FALSE(HasFailure()) << test << " " << absl::StrJoin(intervals_copy, ","); } } TEST(GetIntervalArticulationPointsTest, RandomizedStressTest) { // THIS TEST ASSUMES THAT GetOverlappingIntervalComponents() IS CORRECT. // -> don't look at it if GetOverlappingIntervalComponentsTest.StressTest // fails, and rather investigate that other test first. auto get_num_components = [](const std::vector& intervals) { std::vector mutable_intervals = intervals; std::vector> components; GetOverlappingIntervalComponents(&mutable_intervals, &components); return components.size(); }; // Test duration as of 2021-06: 1s in fastbuild, .4s in opt. constexpr int kNumTests = 10000; absl::BitGen random; for (int test = 0; test < kNumTests; ++test) { const int num_intervals = absl::Uniform(random, 0, 16); const std::vector intervals = GenerateRandomIntervalVector(random, num_intervals); const int baseline_num_components = get_num_components(intervals); // Compute the expected articulation points: try removing each interval // individually and check whether there are more components if we do. std::vector expected_articulation_points; for (int i = 0; i < num_intervals; ++i) { std::vector tmp_intervals = intervals; tmp_intervals.erase(tmp_intervals.begin() + i); if (get_num_components(tmp_intervals) > baseline_num_components) { expected_articulation_points.push_back(i); } } // Sort the articulation points by start, and replace them by their // corresponding IndexedInterval.index. absl::c_sort(expected_articulation_points, [&intervals](int i, int j) { return intervals[i].start < intervals[j].start; }); for (int& idx : expected_articulation_points) idx = intervals[idx].index; // Compare our function with the expected values. std::vector mutable_intervals = intervals; EXPECT_THAT(GetIntervalArticulationPoints(&mutable_intervals), ElementsAreArray(expected_articulation_points)); // Also verify that the function only altered the order of "intervals". EXPECT_THAT(mutable_intervals, UnorderedElementsAreArray(intervals)); ASSERT_FALSE(HasFailure()) << test << " " << absl::StrJoin(intervals, ","); } } TEST(CapacityProfileTest, BasicApi) { CapacityProfile profile; profile.AddRectangle(IntegerValue(2), IntegerValue(6), IntegerValue(0), IntegerValue(2)); profile.AddRectangle(IntegerValue(4), IntegerValue(12), IntegerValue(0), IntegerValue(1)); profile.AddRectangle(IntegerValue(4), IntegerValue(8), IntegerValue(0), IntegerValue(5)); std::vector result; profile.BuildResidualCapacityProfile(&result); EXPECT_THAT( result, ElementsAre( CapacityProfile::Rectangle(kMinIntegerValue, IntegerValue(0)), CapacityProfile::Rectangle(IntegerValue(2), IntegerValue(2)), CapacityProfile::Rectangle(IntegerValue(4), IntegerValue(5)), CapacityProfile::Rectangle(IntegerValue(8), IntegerValue(1)), CapacityProfile::Rectangle(IntegerValue(12), IntegerValue(0)))); // We query it twice to test that it can be done and that the result is not // messed up. profile.BuildResidualCapacityProfile(&result); EXPECT_THAT( result, ElementsAre( CapacityProfile::Rectangle(kMinIntegerValue, IntegerValue(0)), CapacityProfile::Rectangle(IntegerValue(2), IntegerValue(2)), CapacityProfile::Rectangle(IntegerValue(4), IntegerValue(5)), CapacityProfile::Rectangle(IntegerValue(8), IntegerValue(1)), CapacityProfile::Rectangle(IntegerValue(12), IntegerValue(0)))); EXPECT_EQ(IntegerValue(2 * 2 + 4 * 5 + 4 * 1), profile.GetBoundingArea()); } TEST(CapacityProfileTest, ProfileWithMandatoryPart) { CapacityProfile profile; profile.AddRectangle(IntegerValue(2), IntegerValue(6), IntegerValue(0), IntegerValue(2)); profile.AddRectangle(IntegerValue(4), IntegerValue(12), IntegerValue(0), IntegerValue(1)); profile.AddRectangle(IntegerValue(4), IntegerValue(8), IntegerValue(0), IntegerValue(5)); profile.AddMandatoryConsumption(IntegerValue(5), IntegerValue(10), IntegerValue(1)); std::vector result; // Add a dummy rectangle to test the result is cleared. result.push_bask(..); result.push_back( CapacityProfile::Rectangle(IntegerValue(2), IntegerValue(3))); profile.BuildResidualCapacityProfile(&result); EXPECT_THAT( result, ElementsAre( CapacityProfile::Rectangle(kMinIntegerValue, IntegerValue(0)), CapacityProfile::Rectangle(IntegerValue(2), IntegerValue(2)), CapacityProfile::Rectangle(IntegerValue(4), IntegerValue(5)), CapacityProfile::Rectangle(IntegerValue(5), IntegerValue(4)), CapacityProfile::Rectangle(IntegerValue(8), IntegerValue(0)), CapacityProfile::Rectangle(IntegerValue(10), IntegerValue(1)), CapacityProfile::Rectangle(IntegerValue(12), IntegerValue(0)))); // The bounding area should not be impacted by the mandatory consumption. EXPECT_EQ(IntegerValue(2 * 2 + 4 * 5 + 4 * 1), profile.GetBoundingArea()); } IntegerValue NaiveSmallest1DIntersection(IntegerValue range_min, IntegerValue range_max, IntegerValue size, IntegerValue interval_min, IntegerValue interval_max) { IntegerValue min_intersection = std::numeric_limits::max(); for (IntegerValue start = range_min; start + size <= range_max; ++start) { // Interval is [start, start + size] const IntegerValue intersection_start = std::max(start, interval_min); const IntegerValue intersection_end = std::min(start + size, interval_max); const IntegerValue intersection_length = std::max(IntegerValue(0), intersection_end - intersection_start); min_intersection = std::min(min_intersection, intersection_length); } return min_intersection; } TEST(Smallest1DIntersectionTest, BasicTest) { absl::BitGen random; const int64_t max_size = 20; constexpr int num_runs = 400; for (int k = 0; k < num_runs; k++) { const IntegerValue range_min = IntegerValue(absl::Uniform(random, 0, max_size - 1)); const IntegerValue range_max = IntegerValue(absl::Uniform(random, range_min.value() + 1, max_size)); const IntegerValue size = absl::Uniform(random, 1, range_max.value() - range_min.value()); const IntegerValue interval_min = IntegerValue(absl::Uniform(random, 0, max_size - 1)); const IntegerValue interval_max = IntegerValue(absl::Uniform(random, interval_min.value() + 1, max_size)); EXPECT_EQ(NaiveSmallest1DIntersection(range_min, range_max, size, interval_min, interval_max), Smallest1DIntersection(range_min, range_max, size, interval_min, interval_max)); } } TEST(RectangleTest, BasicTest) { Rectangle r1 = {.x_min = 0, .x_max = 2, .y_min = 0, .y_max = 2}; Rectangle r2 = {.x_min = 1, .x_max = 3, .y_min = 1, .y_max = 3}; EXPECT_EQ(r1.Intersect(r2), Rectangle({.x_min = 1, .x_max = 2, .y_min = 1, .y_max = 2})); } TEST(RectangleTest, LineAndPointWorksTest) { const Rectangle vertical_line = { .x_min = 1, .x_max = 1, .y_min = 0, .y_max = 10}; const Rectangle r1 = {.x_min = 0, .x_max = 3, .y_min = 1, .y_max = 3}; EXPECT_FALSE(vertical_line.IsDisjoint(r1)); const Rectangle horizontal_line = { .x_min = 0, .x_max = 10, .y_min = 2, .y_max = 2}; EXPECT_FALSE(vertical_line.IsDisjoint(horizontal_line)); const Rectangle point = {.x_min = 1, .x_max = 1, .y_min = 2, .y_max = 2}; EXPECT_FALSE(r1.IsDisjoint(point)); EXPECT_TRUE(horizontal_line.IsDisjoint(point)); } TEST(RectangleTest, RandomRegionDifferenceTest) { absl::BitGen random; const int64_t size = 20; constexpr int num_runs = 400; for (int k = 0; k < num_runs; k++) { Rectangle ret[2]; for (int i = 0; i < 2; ++i) { ret[i].x_min = IntegerValue(absl::Uniform(random, 0, size - 1)); ret[i].x_max = ret[i].x_min + IntegerValue(absl::Uniform(random, 1, size - 1)); ret[i].y_min = IntegerValue(absl::Uniform(random, 0, size - 1)); ret[i].y_max = ret[i].y_min + IntegerValue(absl::Uniform(random, 1, size - 1)); } auto set_diff = ret[0].RegionDifference(ret[1]); EXPECT_EQ(set_diff.empty(), ret[0].Intersect(ret[1]) == ret[0]); IntegerValue diff_area = 0; for (int i = 0; i < set_diff.size(); ++i) { for (int j = i + 1; j < set_diff.size(); ++j) { EXPECT_TRUE(set_diff[i].IsDisjoint(set_diff[j])); } EXPECT_NE(set_diff[i].Intersect(ret[0]), Rectangle::GetEmpty()); EXPECT_EQ(set_diff[i].Intersect(ret[1]), Rectangle::GetEmpty()); IntegerValue area = set_diff[i].Area(); EXPECT_GT(area, 0); diff_area += area; } EXPECT_EQ(ret[0].IntersectArea(ret[1]) + diff_area, ret[0].Area()); } } TEST(RectangleTest, RandomPavedRegionDifferenceTest) { absl::BitGen random; constexpr int num_runs = 100; for (int k = 0; k < num_runs; k++) { const std::vector set1 = GenerateNonConflictingRectanglesWithPacking({100, 100}, 60, random); const std::vector set2 = GenerateNonConflictingRectanglesWithPacking({100, 100}, 60, random); const std::vector diff = PavedRegionDifference(set1, set2); for (int i = 0; i < diff.size(); ++i) { for (int j = i + 1; j < diff.size(); ++j) { EXPECT_TRUE(diff[i].IsDisjoint(diff[j])); } } for (const auto& r_diff : diff) { for (const auto& r2 : set2) { EXPECT_TRUE(r_diff.IsDisjoint(r2)); } IntegerValue area = 0; for (const auto& r1 : set1) { area += r_diff.IntersectArea(r1); } EXPECT_EQ(area, r_diff.Area()); } } } TEST(GetMinimumOverlapTest, BasicTest) { RectangleInRange range_ret = { .bounding_area = {.x_min = 0, .x_max = 15, .y_min = 0, .y_max = 15}, .x_size = 10, .y_size = 10}; // Minimum intersection is when the item is in the bottom-left corner of the // allowed space. Rectangle r = {.x_min = 3, .x_max = 30, .y_min = 3, .y_max = 30}; EXPECT_EQ(range_ret.GetMinimumIntersection(r).Area(), 7 * 7); EXPECT_EQ(range_ret.GetAtCorner(RectangleInRange::Corner::BOTTOM_LEFT), Rectangle({.x_min = 0, .x_max = 10, .y_min = 0, .y_max = 10})); EXPECT_EQ(range_ret.GetAtCorner(RectangleInRange::Corner::BOTTOM_LEFT) .Intersect(r) .Area(), 7 * 7); EXPECT_EQ(r.Intersect( Rectangle({.x_min = 0, .x_max = 10, .y_min = 0, .y_max = 10})), Rectangle({.x_min = 3, .x_max = 10, .y_min = 3, .y_max = 10})); RectangleInRange bigger = RectangleInRange::BiggestWithMinIntersection(r, range_ret, 7, 7); // This should be a broader range but don't increase the minimum intersection. EXPECT_EQ(bigger.GetMinimumIntersection(r).Area(), 7 * 7); for (const auto& pos : {RectangleInRange::Corner::BOTTOM_LEFT, RectangleInRange::Corner::TOP_LEFT, RectangleInRange::Corner::TOP_RIGHT, RectangleInRange::Corner::BOTTOM_RIGHT}) { EXPECT_EQ(bigger.GetAtCorner(pos).Intersect(r).Area(), 7 * 7); } EXPECT_EQ(bigger.bounding_area.x_min, 0); EXPECT_EQ(bigger.bounding_area.x_max, 33); EXPECT_EQ(bigger.bounding_area.y_min, 0); EXPECT_EQ(bigger.bounding_area.y_max, 33); EXPECT_EQ(r.Intersect(Rectangle( {.x_min = 23, .x_max = 33, .y_min = 23, .y_max = 33})), Rectangle({.x_min = 23, .x_max = 30, .y_min = 23, .y_max = 30})); RectangleInRange range_ret2 = { .bounding_area = {.x_min = 0, .x_max = 105, .y_min = 0, .y_max = 120}, .x_size = 100, .y_size = 100}; Rectangle r2 = {.x_min = 2, .x_max = 4, .y_min = 0, .y_max = 99}; EXPECT_EQ(range_ret2.GetMinimumIntersection(r2), Rectangle::GetEmpty()); } IntegerValue RecomputeEnergy(const Rectangle& rectangle, absl::Span intervals) { IntegerValue ret = 0; for (const RectangleInRange& range : intervals) { const Rectangle min_intersect = range.GetMinimumIntersection(rectangle); EXPECT_LE(min_intersect.SizeX(), range.x_size); EXPECT_LE(min_intersect.SizeY(), range.y_size); ret += min_intersect.Area(); } return ret; } IntegerValue RecomputeEnergy(const ProbingRectangle& ranges) { return RecomputeEnergy(ranges.GetCurrentRectangle(), ranges.Intervals()); } void MoveAndCheck(ProbingRectangle& ranges, ProbingRectangle::Edge type) { EXPECT_TRUE(ranges.CanShrink(type)); const IntegerValue expected_area = ranges.GetCurrentRectangle().Area() - ranges.GetShrinkDeltaArea(type); const IntegerValue expected_min_energy = ranges.GetMinimumEnergy() - ranges.GetShrinkDeltaEnergy(type); ranges.Shrink(type); EXPECT_EQ(ranges.GetMinimumEnergy(), RecomputeEnergy(ranges)); EXPECT_EQ(ranges.GetMinimumEnergy(), expected_min_energy); EXPECT_EQ(ranges.GetCurrentRectangle().Area(), expected_area); ranges.ValidateInvariants(); } TEST(ProbingRectangleTest, BasicTest) { RectangleInRange range_ret = { .bounding_area = {.x_min = 0, .x_max = 15, .y_min = 0, .y_max = 13}, .x_size = 10, .y_size = 8}; RectangleInRange range_ret2 = { .bounding_area = {.x_min = 1, .x_max = 8, .y_min = 7, .y_max = 14}, .x_size = 5, .y_size = 5}; std::vector ranges_vec = {range_ret, range_ret2}; ProbingRectangle ranges(ranges_vec); EXPECT_EQ(ranges.GetCurrentRectangle(), Rectangle({.x_min = 0, .x_max = 15, .y_min = 0, .y_max = 14})); // Start with the full bounding box, thus both are fully inside. EXPECT_EQ(ranges.GetMinimumEnergy(), 10 * 8 + 5 * 5); EXPECT_EQ(ranges.GetMinimumEnergy(), RecomputeEnergy(ranges)); MoveAndCheck(ranges, ProbingRectangle::Edge::LEFT); EXPECT_EQ(ranges.GetCurrentRectangle(), Rectangle({.x_min = 1, .x_max = 15, .y_min = 0, .y_max = 14})); MoveAndCheck(ranges, ProbingRectangle::Edge::LEFT); EXPECT_EQ(ranges.GetCurrentRectangle(), Rectangle({.x_min = 3, .x_max = 15, .y_min = 0, .y_max = 14})); MoveAndCheck(ranges, ProbingRectangle::Edge::LEFT); EXPECT_EQ(ranges.GetCurrentRectangle(), Rectangle({.x_min = 5, .x_max = 15, .y_min = 0, .y_max = 14})); MoveAndCheck(ranges, ProbingRectangle::Edge::LEFT); EXPECT_EQ(ranges.GetCurrentRectangle(), Rectangle({.x_min = 6, .x_max = 15, .y_min = 0, .y_max = 14})); MoveAndCheck(ranges, ProbingRectangle::Edge::TOP); EXPECT_EQ(ranges.GetCurrentRectangle(), Rectangle({.x_min = 6, .x_max = 15, .y_min = 0, .y_max = 13})); MoveAndCheck(ranges, ProbingRectangle::Edge::TOP); EXPECT_EQ(ranges.GetCurrentRectangle(), Rectangle({.x_min = 6, .x_max = 15, .y_min = 0, .y_max = 8})); MoveAndCheck(ranges, ProbingRectangle::Edge::TOP); EXPECT_EQ(ranges.GetCurrentRectangle(), Rectangle({.x_min = 6, .x_max = 15, .y_min = 0, .y_max = 5})); } void ReduceUntilDone(ProbingRectangle& ranges, absl::BitGen& random) { static constexpr ProbingRectangle::Edge kAllEdgesArr[] = { ProbingRectangle::Edge::LEFT, ProbingRectangle::Edge::TOP, ProbingRectangle::Edge::RIGHT, ProbingRectangle::Edge::BOTTOM, }; static constexpr absl::Span kAllMoveTypes( kAllEdgesArr); while (!ranges.IsMinimal()) { ProbingRectangle::Edge type = kAllMoveTypes.at(absl::Uniform(random, 0, (int)kAllMoveTypes.size())); if (!ranges.CanShrink(type)) continue; MoveAndCheck(ranges, type); } } // This function will find the conflicts for rectangles that have as coordinates // for the edges one of {min, min + size, max - size, max} for every possible // item that is at least partially inside the rectangle. Note that we might not // detect a conflict even if there is one by looking only at those rectangles, // see the ProbingRectangleTest.CounterExample unit test for a concrete example. std::optional FindRectangleWithEnergyTooLargeExhaustive( absl::Span box_ranges) { int num_boxes = box_ranges.size(); std::vector x; x.reserve(num_boxes * 4); std::vector y; y.reserve(num_boxes * 4); for (const auto& box : box_ranges) { x.push_back(box.bounding_area.x_min); x.push_back(box.bounding_area.x_min + box.x_size); x.push_back(box.bounding_area.x_max - box.x_size); x.push_back(box.bounding_area.x_max); y.push_back(box.bounding_area.y_min); y.push_back(box.bounding_area.y_min + box.y_size); y.push_back(box.bounding_area.y_max - box.y_size); y.push_back(box.bounding_area.y_max); } std::sort(x.begin(), x.end()); std::sort(y.begin(), y.end()); x.erase(std::unique(x.begin(), x.end()), x.end()); y.erase(std::unique(y.begin(), y.end()), y.end()); for (int i = 0; i < x.size(); ++i) { for (int j = i + 1; j < x.size(); ++j) { for (int k = 0; k < y.size(); ++k) { for (int l = k + 1; l < y.size(); ++l) { IntegerValue used_energy = 0; Rectangle rect = { .x_min = x[i], .x_max = x[j], .y_min = y[k], .y_max = y[l]}; for (const auto& box : box_ranges) { auto intersection = box.GetMinimumIntersection(rect); used_energy += intersection.Area(); } if (used_energy > rect.Area()) { std::vector items_inside; for (const auto& box : box_ranges) { if (box.GetMinimumIntersectionArea(rect) > 0) { items_inside.push_back(box); } } if (items_inside.size() == num_boxes) { return rect; } else { // Call it again after removing items that are outside. auto try2 = FindRectangleWithEnergyTooLargeExhaustive(items_inside); if (try2.has_value()) { return try2; } } } } } } } return std::nullopt; } // This function should give exactly the same result as the // `FindRectangleWithEnergyTooLargeExhaustive` above, but exercising the // `ProbingRectangle` class. std::optional FindRectangleWithEnergyTooLargeWithProbingRectangle( std::vector& box_ranges) { int left_shrinks = 0; int right_shrinks = 0; int top_shrinks = 0; ProbingRectangle ranges(box_ranges); while (true) { // We want to do the equivalent of what // `FindRectangleWithEnergyTooLargeExhaustive` does: for every // left/right/top coordinates, try all possible bottom for conflicts. But // since we cannot fix the coordinates with ProbingRectangle, we fix the // number of shrinks instead. ranges.Reset(); for (int i = 0; i < left_shrinks; i++) { CHECK(ranges.CanShrink(ProbingRectangle::Edge::LEFT)); ranges.Shrink(ProbingRectangle::Edge::LEFT); } const bool left_end = !ranges.CanShrink(ProbingRectangle::Edge::LEFT); for (int i = 0; i < top_shrinks; i++) { CHECK(ranges.CanShrink(ProbingRectangle::Edge::TOP)); ranges.Shrink(ProbingRectangle::Edge::TOP); } const bool top_end = !ranges.CanShrink(ProbingRectangle::Edge::TOP); for (int i = 0; i < right_shrinks; i++) { CHECK(ranges.CanShrink(ProbingRectangle::Edge::RIGHT)); ranges.Shrink(ProbingRectangle::Edge::RIGHT); } const bool right_end = !ranges.CanShrink(ProbingRectangle::Edge::RIGHT); if (ranges.GetMinimumEnergy() > ranges.GetCurrentRectangleArea()) { return ranges.GetCurrentRectangle(); } while (ranges.CanShrink(ProbingRectangle::Edge::BOTTOM)) { ranges.Shrink(ProbingRectangle::Edge::BOTTOM); if (ranges.GetMinimumEnergy() > ranges.GetCurrentRectangleArea()) { return ranges.GetCurrentRectangle(); } } if (!right_end) { right_shrinks++; } else if (!top_end) { top_shrinks++; right_shrinks = 0; } else if (!left_end) { left_shrinks++; top_shrinks = 0; right_shrinks = 0; } else { break; } } return std::nullopt; } TEST(ProbingRectangleTest, Random) { absl::BitGen random; const int64_t size = 20; std::vector rectangles; int count = 0; int comprehensive_count = 0; constexpr int num_runs = 400; for (int k = 0; k < num_runs; k++) { const int num_intervals = absl::Uniform(random, 1, 20); IntegerValue total_area = 0; rectangles.clear(); for (int i = 0; i < num_intervals; ++i) { RectangleInRange& range = rectangles.emplace_back(); range.bounding_area.x_min = IntegerValue(absl::Uniform(random, 0, size)); range.bounding_area.x_max = IntegerValue( absl::Uniform(random, range.bounding_area.x_min.value() + 1, size)); range.x_size = absl::Uniform(random, 1, range.bounding_area.x_max.value() - range.bounding_area.x_min.value()); range.bounding_area.y_min = IntegerValue(absl::Uniform(random, 0, size)); range.bounding_area.y_max = IntegerValue( absl::Uniform(random, range.bounding_area.y_min.value() + 1, size)); range.y_size = absl::Uniform(random, 1, range.bounding_area.y_max.value() - range.bounding_area.y_min.value()); total_area += range.x_size * range.y_size; } auto ret = FindRectanglesWithEnergyConflictMC(rectangles, random, 1.0, 0.8); count += !ret.conflicts.empty(); ProbingRectangle ranges(rectangles); EXPECT_EQ(total_area, ranges.GetMinimumEnergy()); const bool has_possible_conflict = FindRectangleWithEnergyTooLargeExhaustive(rectangles).has_value(); if (has_possible_conflict) { EXPECT_TRUE( FindRectangleWithEnergyTooLargeWithProbingRectangle(rectangles) .has_value()); } ReduceUntilDone(ranges, random); comprehensive_count += has_possible_conflict; } LOG(INFO) << count << "/" << num_runs << " had an heuristic (out of " << comprehensive_count << " possible)."; } // Counterexample for proposition 5.4 of Clautiaux, François, et al. "A new // constraint programming approach for the orthogonal packing problem." // Computers & Operations Research 35.3 (2008): 944-959. TEST(ProbingRectangleTest, CounterExample) { const std::vector rectangles = { {.bounding_area = {.x_min = 6, .x_max = 10, .y_min = 11, .y_max = 16}, .x_size = 3, .y_size = 2}, {.bounding_area = {.x_min = 5, .x_max = 17, .y_min = 12, .y_max = 13}, .x_size = 2, .y_size = 1}, {.bounding_area = {.x_min = 15, .x_max = 18, .y_min = 11, .y_max = 14}, .x_size = 1, .y_size = 1}, {.bounding_area = {.x_min = 4, .x_max = 14, .y_min = 4, .y_max = 19}, .x_size = 8, .y_size = 7}, {.bounding_area = {.x_min = 0, .x_max = 16, .y_min = 5, .y_max = 18}, .x_size = 8, .y_size = 9}, {.bounding_area = {.x_min = 4, .x_max = 14, .y_min = 12, .y_max = 16}, .x_size = 5, .y_size = 1}, {.bounding_area = {.x_min = 1, .x_max = 16, .y_min = 12, .y_max = 18}, .x_size = 6, .y_size = 1}, {.bounding_area = {.x_min = 5, .x_max = 19, .y_min = 14, .y_max = 15}, .x_size = 2, .y_size = 1}}; const Rectangle rect = {.x_min = 6, .x_max = 10, .y_min = 7, .y_max = 16}; // The only other possible rectangle with a conflict is x(7..9), y(7..16), // but none of {y_min, y_min + y_size, y_max - y_size, y_max} is equal to 7. const IntegerValue energy = RecomputeEnergy(rect, rectangles); EXPECT_GT(energy, rect.Area()); EXPECT_FALSE( FindRectangleWithEnergyTooLargeExhaustive(rectangles).has_value()); } std::vector> GetAllIntersections( absl::Span rectangles) { std::vector> result; for (int i = 0; i < rectangles.size(); ++i) { for (int j = i + 1; j < rectangles.size(); ++j) { if (!rectangles[i].IsDisjoint(rectangles[j])) { result.push_back({i, j}); } } } return result; } TEST(FindPartialIntersections, Simple) { Rectangle r1 = {.x_min = 0, .x_max = 2, .y_min = 0, .y_max = 2}; Rectangle r2 = {.x_min = 1, .x_max = 3, .y_min = 1, .y_max = 3}; Rectangle r3 = {.x_min = 5, .x_max = 8, .y_min = 1, .y_max = 3}; EXPECT_THAT(FindPartialRectangleIntersections({r1, r2, r3}), UnorderedElementsAre( testing::AnyOf(std::make_pair(1, 0), std::make_pair(0, 1)))); } bool GraphsDefineSameConnectedComponents( absl::Span> graph1, absl::Span> graph2) { int max = -1; int max2 = -1; for (const auto& [a, b] : graph1) { max = std::max(max, a); max = std::max(max, b); } for (const auto& [a, b] : graph2) { max2 = std::max(max2, a); max2 = std::max(max2, b); } if (max != max2) return false; std::vector> view1(max + 1); std::vector> view2(max + 1); for (const auto& [a, b] : graph1) { view1[a].push_back(b); view1[b].push_back(a); } for (const auto& [a, b] : graph2) { view2[a].push_back(b); view2[b].push_back(a); } std::vector> components1; std::vector> components2; FindStronglyConnectedComponents(max + 1, view1, &components1); FindStronglyConnectedComponents(max + 1, view2, &components2); for (auto& v : components1) { std::sort(v.begin(), v.end()); } for (auto& v : components2) { std::sort(v.begin(), v.end()); } std::sort(components1.begin(), components1.end()); std::sort(components2.begin(), components2.end()); return components1 == components2; } bool HasCycles(absl::Span> graph) { std::vector> view; for (const auto& [a, b] : graph) { if (view.size() <= std::max(a, b)) view.resize(std::max(a, b) + 1); view[a].push_back(b); view[b].push_back(a); } absl::flat_hash_map parent; for (int i = 0; i < view.size(); ++i) { if (parent.contains(i)) continue; std::vector stack; stack.push_back(i); parent[i] = -1; while (!stack.empty()) { const int node = stack.back(); stack.pop_back(); const int cur_parent = parent[node]; for (const int neighbor : view[node]) { if (neighbor == cur_parent) continue; stack.push_back(neighbor); auto [found, _] = parent.insert({neighbor, node}); if (found->second != node) return true; } } } return false; } std::string RenderRectGraph(std::optional bb, absl::Span rectangles, absl::Span> neighbours) { std::stringstream ss; ss << " edge[headclip=false, tailclip=false, penwidth=30];\n"; for (auto [arc1, arc2] : neighbours) { ss << " " << arc1 << "->" << arc2 << " [color=\"black\"];\n"; } return RenderDot(bb, rectangles, ss.str()); } TEST(FindPartialIntersections, Random) { absl::BitGen random; constexpr int num_runs = 100; for (int k = 0; k < num_runs; k++) { std::vector rectangles = GenerateNonConflictingRectanglesWithPacking({100, 100}, 60, random); // We also test FindOneIntersectionIfPresent(). absl::c_sort(rectangles, [](const Rectangle& a, const Rectangle& b) { return a.x_min < b.x_min; }); EXPECT_EQ(FindOneIntersectionIfPresent(rectangles), std::nullopt); const int num_to_grow = absl::Uniform(random, 0, 20); for (int i = 0; i < num_to_grow; ++i) { Rectangle& rec = rectangles[absl::Uniform(random, size_t{0}, rectangles.size())]; rec = {.x_min = rec.x_min - IntegerValue(absl::Uniform(random, 0, 4)), .x_max = rec.x_max + IntegerValue(absl::Uniform(random, 0, 4)), .y_min = rec.y_min - IntegerValue(absl::Uniform(random, 0, 4)), .y_max = rec.y_max + IntegerValue(absl::Uniform(random, 0, 4))}; } const int num_of_rectangle_with_area = rectangles.size(); const int num_points = absl::Uniform(random, 0, 5); for (int i = 0; i < num_points; ++i) { const IntegerValue x = absl::Uniform(random, 0, 100); const IntegerValue y = absl::Uniform(random, 0, 100); rectangles.push_back({.x_min = x, .x_max = x, .y_min = y, .y_max = y}); } const int num_lines = absl::Uniform(random, 0, 5); for (int i = 0; i < num_lines; ++i) { const IntegerValue v = absl::Uniform(random, 0, 100); const IntegerValue i1 = absl::Uniform(random, 0, 99); const IntegerValue i2 = absl::Uniform(random, i1.value() + 1, 100); if (absl::Bernoulli(random, 0.5)) { rectangles.push_back( {.x_min = i1, .x_max = i2, .y_min = v, .y_max = v}); } else { rectangles.push_back( {.x_min = v, .x_max = v, .y_min = i1, .y_max = i2}); } } const std::vector> naive_result = GetAllIntersections(rectangles); const std::vector> result = FindPartialRectangleIntersections(rectangles); for (const auto& [i, j] : result) { EXPECT_FALSE(rectangles[i].IsDisjoint(rectangles[j])); } EXPECT_TRUE(GraphsDefineSameConnectedComponents(naive_result, result)) << RenderRectGraph(std::nullopt, rectangles, result); EXPECT_FALSE(HasCycles(result)) << RenderRectGraph(std::nullopt, rectangles, result); if (k == 0) { LOG(INFO) << RenderRectGraph(std::nullopt, rectangles, result); } // We also test FindOneIntersectionIfPresent(). std::sort(rectangles.begin(), rectangles.begin() + num_of_rectangle_with_area, [](const Rectangle& a, const Rectangle& b) { return a.x_min < b.x_min; }); absl::Span rectangles_with_area = absl::MakeSpan(rectangles).subspan(0, num_of_rectangle_with_area); if (FindPartialRectangleIntersections(rectangles_with_area).empty()) { EXPECT_EQ(FindOneIntersectionIfPresent(rectangles_with_area), std::nullopt); } else { auto opt_pair = FindOneIntersectionIfPresent(rectangles_with_area); EXPECT_NE(opt_pair, std::nullopt); EXPECT_FALSE( rectangles[opt_pair->first].IsDisjoint(rectangles[opt_pair->second])); } } } void BM_FindRectangles(benchmark::State& state) { absl::BitGen random; std::vector> problems; static constexpr int kNumProblems = 20; for (int i = 0; i < kNumProblems; i++) { problems.push_back(MakeItemsFromRectangles( GenerateNonConflictingRectangles(state.range(0), random), state.range(1) / 100.0, random)); } int idx = 0; for (auto s : state) { CHECK(FindRectanglesWithEnergyConflictMC(problems[idx], random, 1.0, 0.8) .conflicts.empty()); ++idx; if (idx == kNumProblems) idx = 0; } } BENCHMARK(BM_FindRectangles) ->ArgPair(5, 1) ->ArgPair(10, 1) ->ArgPair(20, 1) ->ArgPair(30, 1) ->ArgPair(40, 1) ->ArgPair(80, 1) ->ArgPair(100, 1) ->ArgPair(200, 1) ->ArgPair(1000, 1) ->ArgPair(10000, 1) ->ArgPair(5, 100) ->ArgPair(10, 100) ->ArgPair(20, 100) ->ArgPair(30, 100) ->ArgPair(40, 100) ->ArgPair(80, 100) ->ArgPair(100, 100) ->ArgPair(200, 100) ->ArgPair(1000, 100) ->ArgPair(10000, 100); TEST(FindPairwiseRestrictionsTest, Random) { absl::BitGen random; constexpr int num_runs = 400; for (int k = 0; k < num_runs; k++) { const int num_rectangles = absl::Uniform(random, 1, 20); const std::vector rectangles = GenerateNonConflictingRectangles(num_rectangles, random); const std::vector items = GenerateItemsRectanglesWithNoPairwiseConflict( rectangles, absl::Uniform(random, 0, 1.0), random); std::vector results; AppendPairwiseRestrictions(items, &results); for (const PairwiseRestriction& result : results) { EXPECT_NE(result.type, PairwiseRestriction::PairwiseRestrictionType::CONFLICT); } } } void BM_FindPairwiseRestrictions(benchmark::State& state) { absl::BitGen random; // In the vast majority of the cases the propagator doesn't find any pairwise // condition to propagate. Thus we choose to benchmark for this particular // case. const std::vector items = GenerateItemsRectanglesWithNoPairwisePropagation( state.range(0), state.range(1) / 100.0, random); std::vector results; for (auto s : state) { AppendPairwiseRestrictions(items, &results); CHECK(results.empty()); } } BENCHMARK(BM_FindPairwiseRestrictions) ->ArgPair(5, 1) ->ArgPair(10, 1) ->ArgPair(20, 1) ->ArgPair(30, 1) ->ArgPair(40, 1) ->ArgPair(80, 1) ->ArgPair(100, 1) ->ArgPair(200, 1) ->ArgPair(1000, 1) ->ArgPair(10000, 1) ->ArgPair(5, 100) ->ArgPair(10, 100) ->ArgPair(20, 100) ->ArgPair(30, 100) ->ArgPair(40, 100) ->ArgPair(80, 100) ->ArgPair(100, 100) ->ArgPair(200, 100) ->ArgPair(1000, 100) ->ArgPair(10000, 100); void BM_FindPartialIntersectionsSparse(benchmark::State& state) { absl::BitGen random; std::vector> problems; static constexpr int kNumProblems = 10; for (int i = 0; i < kNumProblems; i++) { std::vector& rectangles = problems.emplace_back( GenerateNonConflictingRectangles(state.range(0), random)); const int num_to_grow = absl::Uniform(random, 0, 20); for (int i = 0; i < num_to_grow; ++i) { Rectangle& rec = rectangles[absl::Uniform(random, size_t{0}, rectangles.size())]; rec = {.x_min = rec.x_min - IntegerValue(absl::Uniform(random, 0, 4)), .x_max = rec.x_max + IntegerValue(absl::Uniform(random, 0, 4)), .y_min = rec.y_min - IntegerValue(absl::Uniform(random, 0, 4)), .y_max = rec.y_max + IntegerValue(absl::Uniform(random, 0, 4))}; } } int idx = 0; for (auto s : state) { const std::vector> result = FindPartialRectangleIntersections(problems[idx]); CHECK_LT(result.size(), state.range(0) * state.range(0)); ++idx; if (idx == kNumProblems) idx = 0; } } BENCHMARK(BM_FindPartialIntersectionsSparse) ->Arg(5) ->Arg(10) ->Arg(20) ->Arg(30) ->Arg(40) ->Arg(80) ->Arg(100) ->Arg(200) ->Arg(1000) ->Arg(10000); std::vector GeneratePathologicalCase(int num_rectangles) { std::vector rectangles; for (int i = 0; i < num_rectangles / 2; ++i) { rectangles.push_back({.x_min = 2 * i, .x_max = 2 * i + 1, .y_min = 0, .y_max = 2 * num_rectangles}); rectangles.push_back({ .x_min = 0, .x_max = 2 * num_rectangles, .y_min = 2 * i, .y_max = 2 * i + 1, }); } return rectangles; } void BM_FindPartialIntersectionsPathological(benchmark::State& state) { const std::vector rectangles = GeneratePathologicalCase(state.range(0)); for (auto s : state) { const std::vector> result = FindPartialRectangleIntersections(rectangles); CHECK_LT(result.size(), state.range(0) * state.range(0)); } } BENCHMARK(BM_FindPartialIntersectionsPathological) ->Arg(5) ->Arg(10) ->Arg(20) ->Arg(30) ->Arg(40) ->Arg(80) ->Arg(100) ->Arg(200) ->Arg(1000) ->Arg(10000); std::vector GenerateDenseCase(int num_rectangles) { absl::BitGen random; std::vector rectangles; for (int i = 0; i < num_rectangles; ++i) { const IntegerValue x_min = absl::Uniform(random, 0, num_rectangles); const IntegerValue y_min = absl::Uniform(random, 0, num_rectangles); rectangles.push_back( {.x_min = x_min, .x_max = x_min + absl::Uniform(random, 1, num_rectangles), .y_min = y_min, .y_max = y_min + absl::Uniform(random, 1, num_rectangles)}); } return rectangles; } void BM_FindPartialIntersectionsDense(benchmark::State& state) { absl::BitGen random; std::vector> problems; static constexpr int kNumProblems = 10; for (int i = 0; i < kNumProblems; i++) { problems.push_back(GenerateDenseCase(state.range(0))); } int idx = 0; for (auto s : state) { const std::vector> result = FindPartialRectangleIntersections(problems[idx]); CHECK_LT(result.size(), state.range(0) * state.range(0)); ++idx; if (idx == kNumProblems) idx = 0; } } TEST(FindPartialIntersectionsDenseTest, Random) { const std::vector> result = FindPartialRectangleIntersections(GenerateDenseCase(20)); } BENCHMARK(BM_FindPartialIntersectionsDense) ->Arg(5) ->Arg(10) ->Arg(20) ->Arg(30) ->Arg(40) ->Arg(80) ->Arg(100) ->Arg(200) ->Arg(1000) ->Arg(10000); } // namespace } // namespace sat } // namespace operations_research