diff --git a/include/packingsolver/rectangleguillotine/algorithm_formatter.hpp b/include/packingsolver/rectangleguillotine/algorithm_formatter.hpp index d066a6eb..1a561698 100644 --- a/include/packingsolver/rectangleguillotine/algorithm_formatter.hpp +++ b/include/packingsolver/rectangleguillotine/algorithm_formatter.hpp @@ -39,10 +39,18 @@ class AlgorithmFormatter const Solution& solution, const std::string& s); + /** Update the knapsack bound. */ + void update_knapsack_bound( + Profit profit); + /** Update the bin packing bound. */ void update_bin_packing_bound( BinPos number_of_bins); + /** Update the bin packing bound. */ + void update_variable_sized_bin_packing_bound( + Profit cost); + /** Method to call at the end of the algorithm. */ void end(); diff --git a/src/rectangle/dual_feasible_functions.cpp b/src/rectangle/dual_feasible_functions.cpp index 1016c199..d8ebc507 100644 --- a/src/rectangle/dual_feasible_functions.cpp +++ b/src/rectangle/dual_feasible_functions.cpp @@ -1,6 +1,7 @@ #include "rectangle/dual_feasible_functions.hpp" #include "packingsolver/rectangle/algorithm_formatter.hpp" +#include "packingsolver/rectangle/instance_builder.hpp" using namespace packingsolver; using namespace packingsolver::rectangle; @@ -92,8 +93,96 @@ DualFeasibleFunctionsOutput packingsolver::rectangle::dual_feasible_functions( break; } } - if (!all_items_oriented) + if (!all_items_oriented) { + // If there are some non-oriented items, we use the strategy from + // clautiaux2007: + // - Build a modified instance containing for each item of the original + // instance, one item for each orientation. + // - Compute the bound on the modified instance. + // - Divide it by 2 to get the bound of the original instance. + + BinPos bound = 0; + for (;;) { + // Build modified instance. + InstanceBuilder modified_instance_builder; + modified_instance_builder.set_objective(instance.objective()); + modified_instance_builder.set_parameters(instance.parameters()); + // Add bins and dummy items. + if (bin_type.rect.x == bin_type.rect.y) { + modified_instance_builder.add_bin_type( + bin_type.rect.x, + bin_type.rect.y, + -1, + 2 * instance.number_of_items()); + } else if (bin_type.rect.x > bin_type.rect.y) { + modified_instance_builder.add_bin_type( + bin_type.rect.x, + bin_type.rect.x, + -1, + 2 * instance.number_of_items() + bound); + modified_instance_builder.add_item_type( + bin_type.rect.x, + bin_type.rect.x - bin_type.rect.y, + -1, + bound); + } else if (bin_type.rect.x < bin_type.rect.y) { + modified_instance_builder.add_bin_type( + bin_type.rect.y, + bin_type.rect.y, + -1, + 2 * instance.number_of_items() + bound); + modified_instance_builder.add_item_type( + bin_type.rect.y - bin_type.rect.x, + bin_type.rect.y, + -1, + bound); + } + // Add items. + for (ItemTypeId item_type_id = 0; + item_type_id < instance.number_of_item_types(); + ++item_type_id) { + ItemType item_type = instance.item_type(item_type_id); + item_type.oriented = true; + if (item_type.rect.x == item_type.rect.y) { + modified_instance_builder.add_item_type( + item_type, + item_type.profit, + 2 * item_type.copies); + } else { + modified_instance_builder.add_item_type( + item_type, + item_type.profit, + item_type.copies); + item_type.rect.x = instance.item_type(item_type_id).rect.y; + item_type.rect.y = instance.item_type(item_type_id).rect.x; + modified_instance_builder.add_item_type( + item_type, + item_type.profit, + item_type.copies); + } + } + Instance modified_instance = modified_instance_builder.build(); + + // Compute the bound on the modified instance. + DualFeasibleFunctionsParameters modified_parameters; + modified_parameters.verbosity_level = 0; + auto modified_output = dual_feasible_functions( + modified_instance, + modified_parameters); + + // Retrieve the bound of the original instance. + BinPos bound_cur = (modified_output.bin_packing_bound - 1) / 2 + 1; + if (bound >= bound_cur) + break; + bound = bound_cur; + algorithm_formatter.update_bin_packing_bound(bound); + + if (bin_type.rect.x == bin_type.rect.y) + break; + } + algorithm_formatter.end(); return output; + } // Compute all distinct widths and heights. std::vector widths; diff --git a/src/rectangle/dual_feasible_functions.hpp b/src/rectangle/dual_feasible_functions.hpp index f052465a..e0c2d2f2 100644 --- a/src/rectangle/dual_feasible_functions.hpp +++ b/src/rectangle/dual_feasible_functions.hpp @@ -11,6 +11,9 @@ * - "A theoretical and experimental study of fast lower bounds for the * two-dimensional bin packing problem" (Serairi1 et Haouari, 2018) * https://doi.org/10.1051/ro/2017019 + * - "A new lower bound for the non-oriented two-dimensional bin-packing + * problem☆" (Clautiaux et al., 2007) + * https://doi.org/10.1016/j.orl.2006.07.001 */ #pragma once diff --git a/src/rectangleguillotine/algorithm_formatter.cpp b/src/rectangleguillotine/algorithm_formatter.cpp index 59f43334..cb7957b6 100644 --- a/src/rectangleguillotine/algorithm_formatter.cpp +++ b/src/rectangleguillotine/algorithm_formatter.cpp @@ -232,6 +232,23 @@ void AlgorithmFormatter::update_solution( mutex_.unlock(); } +void AlgorithmFormatter::update_knapsack_bound( + Profit profit) +{ + mutex_.lock(); + if (profit < output_.knapsack_bound) { + output_.knapsack_bound = profit; + output_.json["IntermediaryOutputs"].push_back(output_.to_json()); + parameters_.new_solution_callback(output_); + + // Check optimality. + if (equal(output_.knapsack_bound, output_.solution_pool.best().profit())) { + end_ = true; + } + } + mutex_.unlock(); +} + void AlgorithmFormatter::update_bin_packing_bound( BinPos number_of_bins) { @@ -250,6 +267,24 @@ void AlgorithmFormatter::update_bin_packing_bound( mutex_.unlock(); } +void AlgorithmFormatter::update_variable_sized_bin_packing_bound( + Profit cost) +{ + mutex_.lock(); + if (cost > output_.variable_sized_bin_packing_bound) { + output_.variable_sized_bin_packing_bound = cost; + output_.json["IntermediaryOutputs"].push_back(output_.to_json()); + parameters_.new_solution_callback(output_); + + // Check optimality. + if (output_.solution_pool.best().full() + && equal(output_.variable_sized_bin_packing_bound, output_.solution_pool.best().cost())) { + end_ = true; + } + } + mutex_.unlock(); +} + void AlgorithmFormatter::end() { output_.time = parameters_.timer.elapsed_time(); diff --git a/src/rectangleguillotine/column_generation_2.cpp b/src/rectangleguillotine/column_generation_2.cpp index e55e0630..24a163e0 100644 --- a/src/rectangleguillotine/column_generation_2.cpp +++ b/src/rectangleguillotine/column_generation_2.cpp @@ -1522,6 +1522,14 @@ void column_generation_2_vertical( //std::cout << "callback end" << std::endl; } }; + cgslds_parameters.new_bound_callback = [&instance, &algorithm_formatter]( + const columngenerationsolver::Output& cgs_output) + { + const columngenerationsolver::LimitedDiscrepancySearchOutput& cgslds_output + = static_cast(cgs_output); + double multiplier_profit = largest_power_of_two_lesser_or_equal(instance.largest_item_profit()); + algorithm_formatter.update_knapsack_bound(cgslds_output.bound * multiplier_profit); + }; cgslds_parameters.column_generation_parameters.solver_name = parameters.linear_programming_solver_name; columngenerationsolver::limited_discrepancy_search(cgs_model, cgslds_parameters); @@ -1551,6 +1559,8 @@ void column_generation_2_horizontal( algorithm_formatter.update_solution( solution, ss.str()); + algorithm_formatter.update_knapsack_bound( + flipped_output.knapsack_bound); }; column_generation_2( flipped_instance, diff --git a/src/rectangleguillotine/optimize.cpp b/src/rectangleguillotine/optimize.cpp index 50452ae5..2f52a02c 100644 --- a/src/rectangleguillotine/optimize.cpp +++ b/src/rectangleguillotine/optimize.cpp @@ -159,6 +159,7 @@ void optimize_column_generation_2( std::stringstream ss; ss << "CG"; algorithm_formatter.update_solution(pscg_output.solution_pool.best(), ss.str()); + algorithm_formatter.update_knapsack_bound(pscg_output.knapsack_bound); }; column_generation_2(instance, cg_parameters); } @@ -372,6 +373,24 @@ void optimize_column_generation( algorithm_formatter.update_solution(solution, ss.str()); } }; + cgslds_parameters.new_bound_callback = [&instance, &algorithm_formatter]( + const columngenerationsolver::Output& cgs_output) + { + const columngenerationsolver::LimitedDiscrepancySearchOutput& cgslds_output + = static_cast(cgs_output); + if (instance.objective() == Objective::VariableSizedBinPacking) { + double multiplier_cost = largest_power_of_two_lesser_or_equal(instance.largest_bin_cost()); + algorithm_formatter.update_variable_sized_bin_packing_bound(cgslds_output.bound * multiplier_cost); + } else if (instance.objective() == Objective::Knapsack) { + double multiplier_profit = largest_power_of_two_lesser_or_equal(instance.largest_item_profit()); + algorithm_formatter.update_knapsack_bound(cgslds_output.bound * multiplier_profit); + } else if (instance.objective() == Objective::BinPacking) { + double multiplier_cost = largest_power_of_two_lesser_or_equal(instance.largest_bin_cost()); + BinPos bin_packing_bound = std::ceil(cgslds_output.bound * multiplier_cost / instance.bin_type(0).space() - 0.001); + std::cout << "bin_packing_bound " << bin_packing_bound << std::endl; + algorithm_formatter.update_bin_packing_bound(bin_packing_bound); + } + }; cgslds_parameters.column_generation_parameters.solver_name = parameters.linear_programming_solver_name; columngenerationsolver::limited_discrepancy_search(cgs_model, cgslds_parameters); @@ -407,7 +426,10 @@ packingsolver::rectangleguillotine::Output packingsolver::rectangleguillotine::o if (!use_tree_search && !use_column_generation_2) { use_tree_search = true; - //use_column_generation_2 = true; + //if (instance.number_of_stacks() != instance.number_of_item_types() + // && instance.number_of_defects() == 0) { + // use_column_generation_2 = true; + //} } } else if (instance.objective() == Objective::Knapsack) { // Disable algorithms which are not available for this objective.