diff --git a/examples/cpp/BUILD.bazel b/examples/cpp/BUILD.bazel index a46254c765..732662f162 100644 --- a/examples/cpp/BUILD.bazel +++ b/examples/cpp/BUILD.bazel @@ -30,6 +30,7 @@ cc_binary( "//ortools/packing:binpacking_2d_parser", "//ortools/packing:multiple_dimensions_bin_packing_cc_proto", "//ortools/sat:cp_model", + "@com_google_absl//absl/container:btree", "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/strings", "@com_google_protobuf//:protobuf", diff --git a/examples/cpp/binpacking_2d_sat.cc b/examples/cpp/binpacking_2d_sat.cc index bda8c5dbd2..2212dd9b7f 100644 --- a/examples/cpp/binpacking_2d_sat.cc +++ b/examples/cpp/binpacking_2d_sat.cc @@ -21,6 +21,7 @@ #include #include +#include "absl/container/btree_set.h" #include "absl/flags/flag.h" #include "absl/log/check.h" #include "absl/strings/str_cat.h" @@ -114,20 +115,69 @@ void LoadAndSolve(const std::string& file_name, int instance) { cp_model.AddExactlyOne(item_to_bin[item]); } + absl::btree_set fixed_items; + // We start by fixing big pairwise incompatible items. Each to its own bin. + // See https://arxiv.org/pdf/1909.06835.pdf. + for (int i = 0; i < num_items; ++i) { + if (2 * problem.items(i).shapes(0).dimensions(0) > box_dimensions[0] && + 2 * problem.items(i).shapes(0).dimensions(1) > box_dimensions[1]) { + // Big items are pairwise incompatible. Just fix them in different bins. + fixed_items.insert(i); + } + } + + auto items_are_incompatible = [&problem, &box_dimensions](int i1, int i2) { + return (problem.items(i1).shapes(0).dimensions(0) + + problem.items(i2).shapes(0).dimensions(0) > + box_dimensions[0]) && + (problem.items(i1).shapes(0).dimensions(1) + + problem.items(i2).shapes(0).dimensions(1) > + box_dimensions[1]); + }; + + // This loop looks redundant with the loop above but the order we add the + // items to fixed_items is important. + for (int i = 0; i < num_items; ++i) { + if (fixed_items.contains(i)) { + continue; + } + + bool incompatible_with_all = true; + for (int item : fixed_items) { + if (!items_are_incompatible(item, i)) { + incompatible_with_all = false; + break; + } + } + if (incompatible_with_all) { + fixed_items.insert(i); + } + } + + if (!fixed_items.empty()) { + LOG(INFO) << fixed_items.size() << " items are pairwise incompatible"; + } + // Detect incompatible pairs of items and add conflict at the bin level. int num_incompatible_pairs = 0; for (int i1 = 0; i1 + 1 < num_items; ++i1) { for (int i2 = i1 + 1; i2 < num_items; ++i2) { - if ((problem.items(i1).shapes(0).dimensions(0) + - problem.items(i2).shapes(0).dimensions(0) > - box_dimensions[0]) && - (problem.items(i1).shapes(0).dimensions(1) + - problem.items(i2).shapes(0).dimensions(1) > - box_dimensions[1])) { - num_incompatible_pairs++; - for (int b = 0; b < max_bins; ++b) { - cp_model.AddAtMostOne({item_to_bin[i1][b], item_to_bin[i2][b]}); - } + if (fixed_items.contains(i1) && fixed_items.contains(i2)) { + // Both are already fixed to different bins. + continue; + } + if (!items_are_incompatible(i1, i2)) { + continue; + } + if (num_incompatible_pairs == 0 && fixed_items.empty()) { + // If nothing is already fixed, fix the first incompatible pair to break + // symmetry. + fixed_items.insert(i1); + fixed_items.insert(i2); + } + num_incompatible_pairs++; + for (int b = 0; b < max_bins; ++b) { + cp_model.AddAtMostOne({item_to_bin[i1][b], item_to_bin[i2][b]}); } } } @@ -135,6 +185,15 @@ void LoadAndSolve(const std::string& file_name, int instance) { LOG(INFO) << num_incompatible_pairs << " incompatible pairs of items"; } + // Fix the fixed_items to the first fixed_items.size() bins. + CHECK_LT(fixed_items.size(), max_bins) + << "Infeasible problem, increase max_bins"; + int count = 0; + for (const int item : fixed_items) { + cp_model.FixVariable(item_to_bin[item][count], true); + ++count; + } + // Manages positions and sizes for each item. std::vector>> interval_by_item_bin_dimension(num_items); @@ -213,12 +272,31 @@ void LoadAndSolve(const std::string& file_name, int instance) { } if (absl::GetFlag(FLAGS_symmetry_breaking)) { - // Symmetry breaking: item[i] is in bin <= i for the first max_bins items. - for (int i = 0; i + 1 < std::min(num_items, max_bins); ++i) { - for (int b = i + 1; b < max_bins; ++b) { - cp_model.FixVariable(item_to_bin[i][b], false); + // First sort the items not yet fixed by area. + std::vector not_placed_items; + for (int item = 0; item < num_items; ++item) { + if (!fixed_items.contains(item)) { + not_placed_items.push_back(item); } } + std::sort(not_placed_items.begin(), not_placed_items.end(), + [&problem](int a, int b) { + return problem.items(a).shapes(0).dimensions(0) * + problem.items(a).shapes(0).dimensions(1) > + problem.items(b).shapes(0).dimensions(0) * + problem.items(b).shapes(0).dimensions(1); + }); + + // Symmetry breaking: i-th biggest item is in bin <= i for the first + // max_bins items. + int first_empty_bin = fixed_items.size(); + for (const int item : not_placed_items) { + if (first_empty_bin + 1 >= max_bins) break; + for (int b = first_empty_bin + 1; b < max_bins; ++b) { + cp_model.FixVariable(item_to_bin[item][b], false); + } + ++first_empty_bin; + } } // Setup parameters. diff --git a/examples/cpp/jobshop_sat.cc b/examples/cpp/jobshop_sat.cc index 85c893011e..c848e31ddc 100644 --- a/examples/cpp/jobshop_sat.cc +++ b/examples/cpp/jobshop_sat.cc @@ -748,7 +748,9 @@ void Solve(const JsspInputProblem& problem) { } // Tells the solver we have a makespan objective. + // Also take decision based on precedence, this usually work better. parameters.set_push_all_tasks_toward_start(true); + parameters.set_use_dynamic_precedence_in_disjunctive(true); const CpSolverResponse response = SolveWithParameters(cp_model.Build(), parameters);