From 6a0019138c26302509b2a2aae7dbe19bd20f11ed Mon Sep 17 00:00:00 2001 From: HansBug Date: Mon, 31 Aug 2026 13:31:01 +0800 Subject: [PATCH 1/3] box, boxstacks: handle VariableSizedBinPacking in Solution::operator< The objective is advertised for both solvers and is handled all along their optimization pipelines, but neither comparator had a case for it, so every comparison of two feasible solutions fell through to the default branch and threw, making the objective unusable. Added the missing case to both comparators, using the same cost comparison rectangle::Solution and onedimensional::Solution already use. Both places have to be handled: boxstacks solves an internal box subproblem first, so the unpatched boxstacks binary reports box::Solution, and fixing only box moves the failure one level up to boxstacks::Solution. Added a solution comparison test for the objective to both problem types. The existing boxstacks test does set the objective, but it only builds one solution and asserts bin counts, so it never reaches operator<. --- src/box/solution.cpp | 2 ++ src/boxstacks/solution.cpp | 2 ++ test/box/box_test.cpp | 45 +++++++++++++++++++++++++++++++ test/boxstacks/boxstacks_test.cpp | 41 ++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+) diff --git a/src/box/solution.cpp b/src/box/solution.cpp index 5c1731aa6..4aee7af95 100644 --- a/src/box/solution.cpp +++ b/src/box/solution.cpp @@ -186,6 +186,8 @@ bool Solution::operator<(const Solution& solution) const return strictly_greater_profit(solution.profit(), profit()); } case Objective::Feasibility: { return strictly_greater_profit(solution.profit(), profit()); + } case Objective::VariableSizedBinPacking: { + return strictly_lesser_cost(solution.cost(), cost()); } default: { std::stringstream ss; ss << FUNC_SIGNATURE << ": " diff --git a/src/boxstacks/solution.cpp b/src/boxstacks/solution.cpp index 57c4155be..aca56791a 100644 --- a/src/boxstacks/solution.cpp +++ b/src/boxstacks/solution.cpp @@ -310,6 +310,8 @@ bool Solution::operator<(const Solution& solution) const return strictly_greater_profit(solution.profit(), profit()); } case Objective::Feasibility: { return strictly_greater_profit(solution.profit(), profit()); + } case Objective::VariableSizedBinPacking: { + return strictly_lesser_cost(solution.cost(), cost()); } default: { std::stringstream ss; ss << FUNC_SIGNATURE << ": " diff --git a/test/box/box_test.cpp b/test/box/box_test.cpp index 2fe508795..5826901b5 100644 --- a/test/box/box_test.cpp +++ b/test/box/box_test.cpp @@ -1,3 +1,11 @@ +#include "packingsolver/box/instance_builder.hpp" +#include "packingsolver/box/solution.hpp" +#include "box/solution_builder.hpp" + +#include + +using namespace packingsolver::box; + //#include "packingsolver/box/instance_builder.hpp" //#include "packingsolver/box/optimize.hpp" @@ -58,3 +66,40 @@ // fs::path("data") / "box" / "users" / "2024-11-24" / "parameters.csv", // fs::path("data") / "box" / "users" / "2024-11-24" / "solution.csv", // }})); + +TEST(Box, VariableSizedBinPackingSolutionComparison) +{ + InstanceBuilder instance_builder; + instance_builder.set_objective(packingsolver::Objective::VariableSizedBinPacking); + packingsolver::ItemTypeId item_type_id = instance_builder.add_item_type(6, 5, 5); + instance_builder.set_item_type_copies(item_type_id, 2); + packingsolver::BinTypeId small_bin_type_id = instance_builder.add_bin_type(6, 5, 5); + instance_builder.set_bin_type_cost(small_bin_type_id, 7); + instance_builder.set_bin_type_copies(small_bin_type_id, 2); + packingsolver::BinTypeId large_bin_type_id = instance_builder.add_bin_type(12, 5, 5); + instance_builder.set_bin_type_cost(large_bin_type_id, 10); + instance_builder.set_bin_type_copies(large_bin_type_id, 1); + const Instance instance = instance_builder.build(); + + SolutionBuilder small_bins_solution_builder(instance); + packingsolver::BinPos bin_pos_0 = small_bins_solution_builder.add_bin(small_bin_type_id, 1); + small_bins_solution_builder.add_item(bin_pos_0, item_type_id, {0, 0, 0}, Rotation::XYZ); + packingsolver::BinPos bin_pos_1 = small_bins_solution_builder.add_bin(small_bin_type_id, 1); + small_bins_solution_builder.add_item(bin_pos_1, item_type_id, {0, 0, 0}, Rotation::XYZ); + Solution small_bins_solution = small_bins_solution_builder.build(); + + SolutionBuilder large_bin_solution_builder(instance); + packingsolver::BinPos bin_pos_2 = large_bin_solution_builder.add_bin(large_bin_type_id, 1); + large_bin_solution_builder.add_item(bin_pos_2, item_type_id, {0, 0, 0}, Rotation::XYZ); + large_bin_solution_builder.add_item(bin_pos_2, item_type_id, {6, 0, 0}, Rotation::XYZ); + Solution large_bin_solution = large_bin_solution_builder.build(); + + EXPECT_TRUE(small_bins_solution.feasible()); + EXPECT_TRUE(large_bin_solution.feasible()); + EXPECT_TRUE(packingsolver::equal_cost(small_bins_solution.cost(), 14.0)); + EXPECT_TRUE(packingsolver::equal_cost(large_bin_solution.cost(), 10.0)); + // 'a < b' is true when 'b' is the better solution, so the cost 10 + // solution has to compare greater than the cost 14 one. + EXPECT_TRUE(small_bins_solution < large_bin_solution); + EXPECT_FALSE(large_bin_solution < small_bins_solution); +} diff --git a/test/boxstacks/boxstacks_test.cpp b/test/boxstacks/boxstacks_test.cpp index 57238adc0..3f1e1eef5 100644 --- a/test/boxstacks/boxstacks_test.cpp +++ b/test/boxstacks/boxstacks_test.cpp @@ -23,3 +23,44 @@ TEST(BoxStacks, BinCopies) EXPECT_EQ(solution.number_of_bins(), 2); EXPECT_EQ(solution.bin_copies(0), 2); } + +TEST(BoxStacks, VariableSizedBinPackingSolutionComparison) +{ + InstanceBuilder instance_builder; + instance_builder.set_objective(packingsolver::Objective::VariableSizedBinPacking); + packingsolver::ItemTypeId item_type_id = instance_builder.add_item_type(6, 5, 5); + instance_builder.set_item_type_copies(item_type_id, 2); + packingsolver::BinTypeId small_bin_type_id = instance_builder.add_bin_type(6, 5, 5); + instance_builder.set_bin_type_cost(small_bin_type_id, 7); + instance_builder.set_bin_type_copies(small_bin_type_id, 2); + packingsolver::BinTypeId large_bin_type_id = instance_builder.add_bin_type(12, 5, 5); + instance_builder.set_bin_type_cost(large_bin_type_id, 10); + instance_builder.set_bin_type_copies(large_bin_type_id, 1); + const Instance instance = instance_builder.build(); + + SolutionBuilder small_bins_solution_builder(instance); + packingsolver::BinPos bin_pos_0 = small_bins_solution_builder.add_bin(small_bin_type_id, 1); + packingsolver::StackId stack_id_0 = small_bins_solution_builder.add_stack(bin_pos_0, 0, 6, 0, 5); + small_bins_solution_builder.add_item(bin_pos_0, stack_id_0, item_type_id, Rotation::XYZ); + packingsolver::BinPos bin_pos_1 = small_bins_solution_builder.add_bin(small_bin_type_id, 1); + packingsolver::StackId stack_id_1 = small_bins_solution_builder.add_stack(bin_pos_1, 0, 6, 0, 5); + small_bins_solution_builder.add_item(bin_pos_1, stack_id_1, item_type_id, Rotation::XYZ); + Solution small_bins_solution = small_bins_solution_builder.build(); + + SolutionBuilder large_bin_solution_builder(instance); + packingsolver::BinPos bin_pos_2 = large_bin_solution_builder.add_bin(large_bin_type_id, 1); + packingsolver::StackId stack_id_2 = large_bin_solution_builder.add_stack(bin_pos_2, 0, 6, 0, 5); + large_bin_solution_builder.add_item(bin_pos_2, stack_id_2, item_type_id, Rotation::XYZ); + packingsolver::StackId stack_id_3 = large_bin_solution_builder.add_stack(bin_pos_2, 6, 12, 0, 5); + large_bin_solution_builder.add_item(bin_pos_2, stack_id_3, item_type_id, Rotation::XYZ); + Solution large_bin_solution = large_bin_solution_builder.build(); + + EXPECT_TRUE(small_bins_solution.feasible()); + EXPECT_TRUE(large_bin_solution.feasible()); + EXPECT_TRUE(packingsolver::equal_cost(small_bins_solution.cost(), 14.0)); + EXPECT_TRUE(packingsolver::equal_cost(large_bin_solution.cost(), 10.0)); + // 'a < b' is true when 'b' is the better solution, so the cost 10 + // solution has to compare greater than the cost 14 one. + EXPECT_TRUE(small_bins_solution < large_bin_solution); + EXPECT_FALSE(large_bin_solution < small_bins_solution); +} From 44ff4edfd87bd60c7e144eca5f32ba3c6f85c19f Mon Sep 17 00:00:00 2001 From: HansBug Date: Mon, 31 Aug 2026 18:10:14 +0800 Subject: [PATCH 2/3] box, boxstacks: cover VariableSizedBinPacking with data-driven tests Replaced the two hand-written solution comparison tests with parametrized tests over instance files, following the other parametrized tests of the project. test/box/box_test.cpp: revived the commented-out BoxOptimizeTest. It now reads the reference certificate through SolutionBuilder::read, since the Solution(instance, path) constructor it used no longer exists, and drops the unused defects_path field, matching tree_search_test.cpp. It is instantiated with a variable-sized bin packing instance whose reference solution is proven optimal, its cost of 10 being equal to the bound. test/boxstacks/optimize_test.cpp: new parametrized suite over the same instance. boxstacks has no SolutionBuilder::read, so the returned solution is checked against its expected cost instead of against a reference certificate. --- .../bins.csv | 3 + .../items.csv | 2 + .../parameters.csv | 2 + .../solution.csv | 4 + .../bins.csv | 3 + .../items.csv | 2 + .../parameters.csv | 2 + test/box/box_test.cpp | 140 ++++++------------ test/boxstacks/CMakeLists.txt | 3 +- test/boxstacks/boxstacks_test.cpp | 41 ----- test/boxstacks/optimize_test.cpp | 58 ++++++++ 11 files changed, 126 insertions(+), 134 deletions(-) create mode 100644 data/box/tests/variable_sized_bin_packing_two_bin_types/bins.csv create mode 100644 data/box/tests/variable_sized_bin_packing_two_bin_types/items.csv create mode 100644 data/box/tests/variable_sized_bin_packing_two_bin_types/parameters.csv create mode 100644 data/box/tests/variable_sized_bin_packing_two_bin_types/solution.csv create mode 100644 data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/bins.csv create mode 100644 data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/items.csv create mode 100644 data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/parameters.csv create mode 100644 test/boxstacks/optimize_test.cpp diff --git a/data/box/tests/variable_sized_bin_packing_two_bin_types/bins.csv b/data/box/tests/variable_sized_bin_packing_two_bin_types/bins.csv new file mode 100644 index 000000000..a9e2e4e81 --- /dev/null +++ b/data/box/tests/variable_sized_bin_packing_two_bin_types/bins.csv @@ -0,0 +1,3 @@ +ID,X,Y,Z,COST,COPIES,MAXIMUM_WEIGHT +0,6,5,5,7,2,100 +1,12,5,5,10,1,100 diff --git a/data/box/tests/variable_sized_bin_packing_two_bin_types/items.csv b/data/box/tests/variable_sized_bin_packing_two_bin_types/items.csv new file mode 100644 index 000000000..c5c63346d --- /dev/null +++ b/data/box/tests/variable_sized_bin_packing_two_bin_types/items.csv @@ -0,0 +1,2 @@ +ID,X,Y,Z,ROTATION_XYZ,ROTATION_YXZ,ROTATION_ZYX,ROTATION_YZX,ROTATION_XZY,ROTATION_ZXY,WEIGHT,COPIES +0,6,5,5,1,1,1,1,1,1,1,2 diff --git a/data/box/tests/variable_sized_bin_packing_two_bin_types/parameters.csv b/data/box/tests/variable_sized_bin_packing_two_bin_types/parameters.csv new file mode 100644 index 000000000..fbdd3fc31 --- /dev/null +++ b/data/box/tests/variable_sized_bin_packing_two_bin_types/parameters.csv @@ -0,0 +1,2 @@ +NAME,VALUE +objective,variable-sized-bin-packing diff --git a/data/box/tests/variable_sized_bin_packing_two_bin_types/solution.csv b/data/box/tests/variable_sized_bin_packing_two_bin_types/solution.csv new file mode 100644 index 000000000..3ab474276 --- /dev/null +++ b/data/box/tests/variable_sized_bin_packing_two_bin_types/solution.csv @@ -0,0 +1,4 @@ +TYPE,ID,COPIES,BIN,X,Y,Z,LX,LY,LZ,ROTATION +BIN,1,1,0,0,0,0,12,5,5, +ITEM,0,1,0,0,0,0,6,5,5,XYZ +ITEM,0,1,0,6,0,0,6,5,5,XYZ diff --git a/data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/bins.csv b/data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/bins.csv new file mode 100644 index 000000000..a9e2e4e81 --- /dev/null +++ b/data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/bins.csv @@ -0,0 +1,3 @@ +ID,X,Y,Z,COST,COPIES,MAXIMUM_WEIGHT +0,6,5,5,7,2,100 +1,12,5,5,10,1,100 diff --git a/data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/items.csv b/data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/items.csv new file mode 100644 index 000000000..fe3079cb3 --- /dev/null +++ b/data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/items.csv @@ -0,0 +1,2 @@ +ID,X,Y,Z,ROTATION_XYZ,ROTATION_YXZ,ROTATION_ZYX,ROTATION_YZX,ROTATION_XZY,ROTATION_ZXY,WEIGHT,COPIES,GROUP_ID,STACKABILITY_ID,NESTING_HEIGHT,MAXIMUM_STACKABILITY,MAXIMUM_WEIGHT_ABOVE +0,6,5,5,1,1,1,1,1,1,1,2,0,0,0,1,100 diff --git a/data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/parameters.csv b/data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/parameters.csv new file mode 100644 index 000000000..fbdd3fc31 --- /dev/null +++ b/data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/parameters.csv @@ -0,0 +1,2 @@ +NAME,VALUE +objective,variable-sized-bin-packing diff --git a/test/box/box_test.cpp b/test/box/box_test.cpp index 5826901b5..d214e4c2d 100644 --- a/test/box/box_test.cpp +++ b/test/box/box_test.cpp @@ -1,105 +1,61 @@ #include "packingsolver/box/instance_builder.hpp" -#include "packingsolver/box/solution.hpp" +#include "packingsolver/box/optimize.hpp" #include "box/solution_builder.hpp" #include +#include using namespace packingsolver::box; +namespace fs = boost::filesystem; -//#include "packingsolver/box/instance_builder.hpp" -//#include "packingsolver/box/optimize.hpp" - -//#include -//#include - -//using namespace packingsolver::box; -//namespace fs = boost::filesystem; - -//struct BoxOptimizeTestParams -//{ -// fs::path items_path; -// fs::path bins_path; -// fs::path defects_path; -// fs::path parameters_path; -// fs::path certificate_path; -//}; - -//inline std::ostream& operator<<(std::ostream& os, const BoxOptimizeTestParams& test_params) -//{ -// os << test_params.items_path; -// return os; -//} - -//class BoxOptimizeTest: public testing::TestWithParam { }; - -//TEST_P(BoxOptimizeTest, BoxOptimize) -//{ -// BoxOptimizeTestParams test_params = GetParam(); -// InstanceBuilder instance_builder; -// instance_builder.read_item_types(test_params.items_path.string()); -// instance_builder.read_bin_types(test_params.bins_path.string()); -// instance_builder.read_parameters(test_params.parameters_path.string()); -// Instance instance = instance_builder.build(); - -// OptimizeParameters optimize_parameters; -// optimize_parameters.optimization_mode = packingsolver::OptimizationMode::NotAnytimeSequential; -// Output output = optimize(instance, optimize_parameters); - -// Solution solution(instance, test_params.certificate_path.string()); -// std::cout << std::endl -// << "Reference solution" << std::endl -// << "------------------" << std::endl; -// solution.format(std::cout); +struct BoxOptimizeTestParams +{ + fs::path items_path; + fs::path bins_path; + fs::path parameters_path; + fs::path certificate_path; +}; -// EXPECT_EQ(!(output.solution_pool.best() < solution), true); -// EXPECT_EQ(!(solution < output.solution_pool.best()), true); -//} +inline std::ostream& operator<<(std::ostream& os, const BoxOptimizeTestParams& test_params) +{ + os << test_params.items_path; + return os; +} -//INSTANTIATE_TEST_SUITE_P( -// Box, -// BoxOptimizeTest, -// testing::ValuesIn(std::vector{ -// { -// fs::path("data") / "box" / "users" / "2024-11-24" / "items.csv", -// fs::path("data") / "box" / "users" / "2024-11-24" / "bins.csv", -// fs::path(""), -// fs::path("data") / "box" / "users" / "2024-11-24" / "parameters.csv", -// fs::path("data") / "box" / "users" / "2024-11-24" / "solution.csv", -// }})); +class BoxOptimizeTest: public testing::TestWithParam { }; -TEST(Box, VariableSizedBinPackingSolutionComparison) +TEST_P(BoxOptimizeTest, BoxOptimize) { + BoxOptimizeTestParams test_params = GetParam(); InstanceBuilder instance_builder; - instance_builder.set_objective(packingsolver::Objective::VariableSizedBinPacking); - packingsolver::ItemTypeId item_type_id = instance_builder.add_item_type(6, 5, 5); - instance_builder.set_item_type_copies(item_type_id, 2); - packingsolver::BinTypeId small_bin_type_id = instance_builder.add_bin_type(6, 5, 5); - instance_builder.set_bin_type_cost(small_bin_type_id, 7); - instance_builder.set_bin_type_copies(small_bin_type_id, 2); - packingsolver::BinTypeId large_bin_type_id = instance_builder.add_bin_type(12, 5, 5); - instance_builder.set_bin_type_cost(large_bin_type_id, 10); - instance_builder.set_bin_type_copies(large_bin_type_id, 1); - const Instance instance = instance_builder.build(); - - SolutionBuilder small_bins_solution_builder(instance); - packingsolver::BinPos bin_pos_0 = small_bins_solution_builder.add_bin(small_bin_type_id, 1); - small_bins_solution_builder.add_item(bin_pos_0, item_type_id, {0, 0, 0}, Rotation::XYZ); - packingsolver::BinPos bin_pos_1 = small_bins_solution_builder.add_bin(small_bin_type_id, 1); - small_bins_solution_builder.add_item(bin_pos_1, item_type_id, {0, 0, 0}, Rotation::XYZ); - Solution small_bins_solution = small_bins_solution_builder.build(); - - SolutionBuilder large_bin_solution_builder(instance); - packingsolver::BinPos bin_pos_2 = large_bin_solution_builder.add_bin(large_bin_type_id, 1); - large_bin_solution_builder.add_item(bin_pos_2, item_type_id, {0, 0, 0}, Rotation::XYZ); - large_bin_solution_builder.add_item(bin_pos_2, item_type_id, {6, 0, 0}, Rotation::XYZ); - Solution large_bin_solution = large_bin_solution_builder.build(); - - EXPECT_TRUE(small_bins_solution.feasible()); - EXPECT_TRUE(large_bin_solution.feasible()); - EXPECT_TRUE(packingsolver::equal_cost(small_bins_solution.cost(), 14.0)); - EXPECT_TRUE(packingsolver::equal_cost(large_bin_solution.cost(), 10.0)); - // 'a < b' is true when 'b' is the better solution, so the cost 10 - // solution has to compare greater than the cost 14 one. - EXPECT_TRUE(small_bins_solution < large_bin_solution); - EXPECT_FALSE(large_bin_solution < small_bins_solution); + instance_builder.read_item_types(test_params.items_path.string()); + instance_builder.read_bin_types(test_params.bins_path.string()); + instance_builder.read_parameters(test_params.parameters_path.string()); + Instance instance = instance_builder.build(); + + OptimizeParameters optimize_parameters; + optimize_parameters.optimization_mode = packingsolver::OptimizationMode::NotAnytimeSequential; + Output output = optimize(instance, optimize_parameters); + + SolutionBuilder solution_builder(instance); + solution_builder.read(test_params.certificate_path.string()); + Solution solution = solution_builder.build(); + std::cout << std::endl + << "Reference solution" << std::endl + << "------------------" << std::endl; + solution.format(std::cout); + + EXPECT_EQ(!(output.solution_pool.best() < solution), true); + EXPECT_EQ(!(solution < output.solution_pool.best()), true); } + +INSTANTIATE_TEST_SUITE_P( + Box, + BoxOptimizeTest, + testing::ValuesIn(std::vector{ + { + fs::path("data") / "box" / "tests" / "variable_sized_bin_packing_two_bin_types" / "items.csv", + fs::path("data") / "box" / "tests" / "variable_sized_bin_packing_two_bin_types" / "bins.csv", + fs::path("data") / "box" / "tests" / "variable_sized_bin_packing_two_bin_types" / "parameters.csv", + fs::path("data") / "box" / "tests" / "variable_sized_bin_packing_two_bin_types" / "solution.csv", + }})); diff --git a/test/boxstacks/CMakeLists.txt b/test/boxstacks/CMakeLists.txt index 7c916e699..9648c537d 100644 --- a/test/boxstacks/CMakeLists.txt +++ b/test/boxstacks/CMakeLists.txt @@ -1,7 +1,8 @@ add_executable(PackingSolver_boxstacks_test) target_sources(PackingSolver_boxstacks_test PRIVATE boxstacks_test.cpp - sequential_onedimensional_rectangle_test.cpp) + sequential_onedimensional_rectangle_test.cpp + optimize_test.cpp) target_include_directories(PackingSolver_boxstacks_test PRIVATE ${PROJECT_SOURCE_DIR}/src) target_link_libraries(PackingSolver_boxstacks_test diff --git a/test/boxstacks/boxstacks_test.cpp b/test/boxstacks/boxstacks_test.cpp index 3f1e1eef5..57238adc0 100644 --- a/test/boxstacks/boxstacks_test.cpp +++ b/test/boxstacks/boxstacks_test.cpp @@ -23,44 +23,3 @@ TEST(BoxStacks, BinCopies) EXPECT_EQ(solution.number_of_bins(), 2); EXPECT_EQ(solution.bin_copies(0), 2); } - -TEST(BoxStacks, VariableSizedBinPackingSolutionComparison) -{ - InstanceBuilder instance_builder; - instance_builder.set_objective(packingsolver::Objective::VariableSizedBinPacking); - packingsolver::ItemTypeId item_type_id = instance_builder.add_item_type(6, 5, 5); - instance_builder.set_item_type_copies(item_type_id, 2); - packingsolver::BinTypeId small_bin_type_id = instance_builder.add_bin_type(6, 5, 5); - instance_builder.set_bin_type_cost(small_bin_type_id, 7); - instance_builder.set_bin_type_copies(small_bin_type_id, 2); - packingsolver::BinTypeId large_bin_type_id = instance_builder.add_bin_type(12, 5, 5); - instance_builder.set_bin_type_cost(large_bin_type_id, 10); - instance_builder.set_bin_type_copies(large_bin_type_id, 1); - const Instance instance = instance_builder.build(); - - SolutionBuilder small_bins_solution_builder(instance); - packingsolver::BinPos bin_pos_0 = small_bins_solution_builder.add_bin(small_bin_type_id, 1); - packingsolver::StackId stack_id_0 = small_bins_solution_builder.add_stack(bin_pos_0, 0, 6, 0, 5); - small_bins_solution_builder.add_item(bin_pos_0, stack_id_0, item_type_id, Rotation::XYZ); - packingsolver::BinPos bin_pos_1 = small_bins_solution_builder.add_bin(small_bin_type_id, 1); - packingsolver::StackId stack_id_1 = small_bins_solution_builder.add_stack(bin_pos_1, 0, 6, 0, 5); - small_bins_solution_builder.add_item(bin_pos_1, stack_id_1, item_type_id, Rotation::XYZ); - Solution small_bins_solution = small_bins_solution_builder.build(); - - SolutionBuilder large_bin_solution_builder(instance); - packingsolver::BinPos bin_pos_2 = large_bin_solution_builder.add_bin(large_bin_type_id, 1); - packingsolver::StackId stack_id_2 = large_bin_solution_builder.add_stack(bin_pos_2, 0, 6, 0, 5); - large_bin_solution_builder.add_item(bin_pos_2, stack_id_2, item_type_id, Rotation::XYZ); - packingsolver::StackId stack_id_3 = large_bin_solution_builder.add_stack(bin_pos_2, 6, 12, 0, 5); - large_bin_solution_builder.add_item(bin_pos_2, stack_id_3, item_type_id, Rotation::XYZ); - Solution large_bin_solution = large_bin_solution_builder.build(); - - EXPECT_TRUE(small_bins_solution.feasible()); - EXPECT_TRUE(large_bin_solution.feasible()); - EXPECT_TRUE(packingsolver::equal_cost(small_bins_solution.cost(), 14.0)); - EXPECT_TRUE(packingsolver::equal_cost(large_bin_solution.cost(), 10.0)); - // 'a < b' is true when 'b' is the better solution, so the cost 10 - // solution has to compare greater than the cost 14 one. - EXPECT_TRUE(small_bins_solution < large_bin_solution); - EXPECT_FALSE(large_bin_solution < small_bins_solution); -} diff --git a/test/boxstacks/optimize_test.cpp b/test/boxstacks/optimize_test.cpp new file mode 100644 index 000000000..e215ec947 --- /dev/null +++ b/test/boxstacks/optimize_test.cpp @@ -0,0 +1,58 @@ +#include "packingsolver/boxstacks/instance_builder.hpp" +#include "packingsolver/boxstacks/optimize.hpp" + +#include +#include + +using namespace packingsolver::boxstacks; +namespace fs = boost::filesystem; + +struct BoxStacksOptimizeTestParams +{ + fs::path items_path; + fs::path bins_path; + fs::path parameters_path; + + /** Expected cost of the returned solution. */ + packingsolver::Profit cost; +}; + +inline std::ostream& operator<<(std::ostream& os, const BoxStacksOptimizeTestParams& test_params) +{ + os << test_params.items_path; + return os; +} + +class BoxStacksOptimizeTest: public testing::TestWithParam { }; + +TEST_P(BoxStacksOptimizeTest, BoxStacksOptimize) +{ + BoxStacksOptimizeTestParams test_params = GetParam(); + InstanceBuilder instance_builder; + instance_builder.read_item_types(test_params.items_path.string()); + instance_builder.read_bin_types(test_params.bins_path.string()); + instance_builder.read_parameters(test_params.parameters_path.string()); + Instance instance = instance_builder.build(); + + OptimizeParameters optimize_parameters; + optimize_parameters.optimization_mode = packingsolver::OptimizationMode::NotAnytimeSequential; + Output output = optimize(instance, optimize_parameters); + + // 'boxstacks' has no 'SolutionBuilder::read', so the returned solution is + // checked against the expected cost instead of against a reference + // certificate. + EXPECT_TRUE(output.solution_pool.best().feasible()); + EXPECT_TRUE(output.solution_pool.best().full()); + EXPECT_TRUE(packingsolver::equal_cost(output.solution_pool.best().cost(), test_params.cost)); +} + +INSTANTIATE_TEST_SUITE_P( + BoxStacks, + BoxStacksOptimizeTest, + testing::ValuesIn(std::vector{ + { + fs::path("data") / "boxstacks" / "tests" / "variable_sized_bin_packing_two_bin_types" / "items.csv", + fs::path("data") / "boxstacks" / "tests" / "variable_sized_bin_packing_two_bin_types" / "bins.csv", + fs::path("data") / "boxstacks" / "tests" / "variable_sized_bin_packing_two_bin_types" / "parameters.csv", + 10, + }})); From d4fa630ff8a6f30f0dd9b9dc6fcd37e5f5c09249 Mon Sep 17 00:00:00 2001 From: Florian Fontan Date: Sun, 6 Sep 2026 23:08:45 +0200 Subject: [PATCH 3/3] boxstacks: compare against a certificate now that SolutionBuilder::read exists boxstacks::SolutionBuilder::read() and the ROTATION column in Solution::write() landed in master after this branch was created (PR #541), so BoxStacksOptimizeTest could only check the returned solution's cost. Switch it to read a reference solution.csv and compare via Solution::operator< equivalence, matching BoxOptimizeTest and the other data-driven tests in the project. --- .../solution.csv | 6 +++++ test/boxstacks/optimize_test.cpp | 23 +++++++++++-------- 2 files changed, 19 insertions(+), 10 deletions(-) create mode 100644 data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/solution.csv diff --git a/data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/solution.csv b/data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/solution.csv new file mode 100644 index 000000000..a69e7ac19 --- /dev/null +++ b/data/boxstacks/tests/variable_sized_bin_packing_two_bin_types/solution.csv @@ -0,0 +1,6 @@ +TYPE,ID,COPIES,BIN,STACK,X,Y,Z,LX,LY,LZ,GROUP_ID,ROTATION +BIN,1,1,0,-1,0,0,0,12,5,5,, +STACK,0,1,0,0,0,0,0,6,5,5,, +ITEM,0,1,0,0,0,0,0,6,5,5,0,XYZ +STACK,1,1,0,1,6,0,0,6,5,5,, +ITEM,0,1,0,1,6,0,0,6,5,5,0,XYZ diff --git a/test/boxstacks/optimize_test.cpp b/test/boxstacks/optimize_test.cpp index e215ec947..7c5acbfdf 100644 --- a/test/boxstacks/optimize_test.cpp +++ b/test/boxstacks/optimize_test.cpp @@ -1,5 +1,6 @@ #include "packingsolver/boxstacks/instance_builder.hpp" #include "packingsolver/boxstacks/optimize.hpp" +#include "boxstacks/solution_builder.hpp" #include #include @@ -12,9 +13,7 @@ struct BoxStacksOptimizeTestParams fs::path items_path; fs::path bins_path; fs::path parameters_path; - - /** Expected cost of the returned solution. */ - packingsolver::Profit cost; + fs::path certificate_path; }; inline std::ostream& operator<<(std::ostream& os, const BoxStacksOptimizeTestParams& test_params) @@ -38,12 +37,16 @@ TEST_P(BoxStacksOptimizeTest, BoxStacksOptimize) optimize_parameters.optimization_mode = packingsolver::OptimizationMode::NotAnytimeSequential; Output output = optimize(instance, optimize_parameters); - // 'boxstacks' has no 'SolutionBuilder::read', so the returned solution is - // checked against the expected cost instead of against a reference - // certificate. - EXPECT_TRUE(output.solution_pool.best().feasible()); - EXPECT_TRUE(output.solution_pool.best().full()); - EXPECT_TRUE(packingsolver::equal_cost(output.solution_pool.best().cost(), test_params.cost)); + SolutionBuilder solution_builder(instance); + solution_builder.read(test_params.certificate_path.string()); + Solution solution = solution_builder.build(); + std::cout << std::endl + << "Reference solution" << std::endl + << "------------------" << std::endl; + solution.format(std::cout); + + EXPECT_EQ(!(output.solution_pool.best() < solution), true); + EXPECT_EQ(!(solution < output.solution_pool.best()), true); } INSTANTIATE_TEST_SUITE_P( @@ -54,5 +57,5 @@ INSTANTIATE_TEST_SUITE_P( fs::path("data") / "boxstacks" / "tests" / "variable_sized_bin_packing_two_bin_types" / "items.csv", fs::path("data") / "boxstacks" / "tests" / "variable_sized_bin_packing_two_bin_types" / "bins.csv", fs::path("data") / "boxstacks" / "tests" / "variable_sized_bin_packing_two_bin_types" / "parameters.csv", - 10, + fs::path("data") / "boxstacks" / "tests" / "variable_sized_bin_packing_two_bin_types" / "solution.csv", }}));