From afa29bc06a726ed0d45ffe42dce76e928b1ed031 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Sun, 3 Dec 2023 16:59:16 +0100 Subject: [PATCH] polish examples --- examples/cpp/binpacking_2d_sat.cc | 55 +++++++++++++++++++++++-------- examples/cpp/costas_array_sat.cc | 3 +- examples/python/appointments.py | 12 +++---- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/examples/cpp/binpacking_2d_sat.cc b/examples/cpp/binpacking_2d_sat.cc index b50808fab1..682bffc897 100644 --- a/examples/cpp/binpacking_2d_sat.cc +++ b/examples/cpp/binpacking_2d_sat.cc @@ -170,6 +170,22 @@ absl::btree_set FindFixedItems( return fixed_items; } +// Solves a subset sum problem to find the maximum reachable max size. +int64_t MaxSubsetSumSize(const std::vector& sizes, int64_t max_size) { + CpModelBuilder builder; + LinearExpr weighed_sum; + for (const int size : sizes) { + BoolVar var = builder.NewBoolVar(); + weighed_sum += size * var; + } + + builder.AddLessOrEqual(weighed_sum, max_size); + builder.Maximize(weighed_sum); + + const CpSolverResponse response = Solve(builder.Build()); + return static_cast(response.objective_value()); +} + } // namespace // Load a 2D bin packing problem and solve it. @@ -184,8 +200,8 @@ void LoadAndSolve(const std::string& file_name, int instance) { << file_name << "'"; LOG(INFO) << "Instance has " << problem.items_size() << " items"; - const auto bin_sizes = problem.box_shape().dimensions(); - const int num_dimensions = bin_sizes.size(); + const auto original_bin_sizes = problem.box_shape().dimensions(); + const int num_dimensions = original_bin_sizes.size(); const int num_items = problem.items_size(); // Non overlapping. @@ -195,14 +211,23 @@ void LoadAndSolve(const std::string& file_name, int instance) { LOG(FATAL) << num_dimensions << " dimensions not supported."; } - const int64_t area_of_one_bin = bin_sizes[0] * bin_sizes[1]; + // Reduce the size of the box with subset-sum. + std::vector x_sizes; + std::vector y_sizes; int64_t sum_of_items_area = 0; for (const auto& item : problem.items()) { CHECK_EQ(1, item.shapes_size()); const auto& shape = item.shapes(0); CHECK_EQ(2, shape.dimensions_size()); sum_of_items_area += shape.dimensions(0) * shape.dimensions(1); + x_sizes.push_back(shape.dimensions(0)); + y_sizes.push_back(shape.dimensions(1)); } + std::vector bin_sizes(2); + bin_sizes[0] = MaxSubsetSumSize(x_sizes, original_bin_sizes[0]); + bin_sizes[1] = MaxSubsetSumSize(y_sizes, original_bin_sizes[1]); + + const int64_t area_of_one_bin = bin_sizes[0] * bin_sizes[1]; const int64_t trivial_lb = CeilOfRatio(sum_of_items_area, area_of_one_bin); LOG(INFO) << "Trivial lower bound of the number of bins = " << trivial_lb; @@ -270,8 +295,7 @@ void LoadAndSolve(const std::string& file_name, int instance) { } // Compute the min size of all items in each dimension. - std::vector min_sizes_per_dimension = {bin_sizes.begin(), - bin_sizes.end()}; + std::vector min_sizes_per_dimension = bin_sizes; for (int item = 0; item < num_items; ++item) { for (int dim = 0; dim < num_dimensions; ++dim) { min_sizes_per_dimension[dim] = @@ -295,14 +319,12 @@ void LoadAndSolve(const std::string& file_name, int instance) { const int64_t size = problem.items(item).shapes(0).dimensions(dim); IntVar start; if (b == 0) { - // For item fixed to a given bin, by symmetry of rotation we can also - // assume it is in the lower left corner. - // Note that the data defines the global size, so the range of the - // interval is [0, bin_size - 1]. + // For item fixed to a given bin, by symmetry, we can also assume it + // is in the lower left corner. const int64_t start_max = fixed_items.contains(item) - ? (bin_size - size) / 2 - : bin_size - 1 - size; - start = cp_model.NewIntVar({0, start_max}); + ? (bin_size - size + 1) / 2 + : bin_size - size; + start = cp_model.NewIntVar({0, start_max}); starts_by_dimension[item][dim] = start; if (size + min_sizes_per_dimension[dim] > bin_size) { @@ -351,7 +373,14 @@ void LoadAndSolve(const std::string& file_name, int instance) { } // Non overlapping. - LOG(INFO) << "Box size: " << bin_sizes[0] << "*" << bin_sizes[1]; + if (bin_sizes[0] == original_bin_sizes[0] && + bin_sizes[1] == original_bin_sizes[1]) { + LOG(INFO) << "Box size: [" << bin_sizes[0] << " * " << bin_sizes[1] << "]"; + } else { + LOG(INFO) << "Box size: [" << bin_sizes[0] << " * " << bin_sizes[1] + << "] reduced from [" << original_bin_sizes[0] << " * " + << original_bin_sizes[1] << "]"; + } for (int b = 0; b < max_bins; ++b) { NoOverlap2DConstraint no_overlap_2d = cp_model.AddNoOverlap2D(); for (int item = 0; item < num_items; ++item) { diff --git a/examples/cpp/costas_array_sat.cc b/examples/cpp/costas_array_sat.cc index 4c8b07e152..56f0a5dede 100644 --- a/examples/cpp/costas_array_sat.cc +++ b/examples/cpp/costas_array_sat.cc @@ -31,6 +31,7 @@ #include "absl/flags/flag.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" +#include "absl/types/span.h" #include "ortools/base/init_google.h" #include "ortools/base/logging.h" #include "ortools/base/types.h" @@ -48,7 +49,7 @@ namespace operations_research { namespace sat { // Checks that all pairwise distances are unique and returns all violators -void CheckConstraintViolators(const std::vector& vars, +void CheckConstraintViolators(absl::Span vars, std::vector* const violators) { int dim = vars.size(); diff --git a/examples/python/appointments.py b/examples/python/appointments.py index 017e8c9009..8f16cccfe1 100644 --- a/examples/python/appointments.py +++ b/examples/python/appointments.py @@ -54,7 +54,7 @@ class AllSolutionCollector(cp_model.CpSolverSolutionCallback): return self.__collect -def EnumerateAllKnapsacksWithRepetition( +def enumerate_all_knapsacks_with_repetition( item_sizes: list[int], total_size_min: int, total_size_max: int ) -> list[list[int]]: """Enumerate all possible knapsacks with total size in the given range. @@ -85,7 +85,7 @@ def EnumerateAllKnapsacksWithRepetition( return solution_collector.combinations() -def AggregateItemCollectionsOptimally( +def aggregate_item_collections_optimally( item_collections: list[list[int]], max_num_collections: int, ideal_item_ratios: list[float], @@ -179,7 +179,7 @@ def AggregateItemCollectionsOptimally( return [] -def GetOptimalSchedule( +def get_optimal_schedule( demand: list[tuple[float, str, int]] ) -> list[tuple[int, list[tuple[int, str]]]]: """Computes the optimal schedule for the installation input. @@ -193,7 +193,7 @@ def GetOptimalSchedule( Returns: The same output type as EnumerateAllKnapsacksWithRepetition. """ - combinations = EnumerateAllKnapsacksWithRepetition( + combinations = enumerate_all_knapsacks_with_repetition( [a[2] + _COMMUTE_TIME.value for a in demand], _LOAD_MIN.value, _LOAD_MAX.value, @@ -205,7 +205,7 @@ def GetOptimalSchedule( ) ) - selection = AggregateItemCollectionsOptimally( + selection = aggregate_item_collections_optimally( combinations, _NUM_WORKERS.value, [a[0] / 100.0 for a in demand] ) output = [] @@ -237,7 +237,7 @@ def main(_): % (_LOAD_MIN.value, _LOAD_MAX.value) ) print("%d workers" % _NUM_WORKERS.value) - selection = GetOptimalSchedule(demand) + selection = get_optimal_schedule(demand) print() installed = 0 installed_per_type = {}