improve knapsack_2d_sat.cc and binpacking_2d_sat.cc code

This commit is contained in:
Laurent Perron
2021-12-22 09:53:33 +01:00
parent 83f331cca5
commit 9d79484058
2 changed files with 73 additions and 65 deletions

View File

@@ -46,68 +46,75 @@ namespace sat {
void LoadAndSolve(const std::string& file_name, int instance) {
packing::BinPacking2dParser parser;
if (!parser.Load2BPFile(file_name, instance)) {
LOG(FATAL) << "Cannot read instance " << instance << " from file "
<< file_name;
LOG(FATAL) << "Cannot read instance " << instance << " from file '"
<< file_name << "'";
}
packing::MultipleDimensionsBinPackingProblem problem = parser.problem();
LOG(INFO) << "Successfully loaded instance " << instance << " from file "
<< file_name;
LOG(INFO) << "Successfully loaded instance " << instance << " from file '"
<< file_name << "'";
LOG(INFO) << "Instance has " << problem.items_size() << " items";
const auto box_dimensions = problem.box_shape().dimensions();
const int num_dimensions = box_dimensions.size();
const int num_items = problem.items_size();
const int area_of_one_bin = box_dimensions[0] * box_dimensions[1];
int sum_of_items_area = 0;
const int64_t area_of_one_bin = box_dimensions[0] * box_dimensions[1];
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);
}
const int trivial_lb =
// Take the ceil of the ratio.
const int64_t trivial_lb =
(sum_of_items_area + area_of_one_bin - 1) / area_of_one_bin;
LOG(INFO) << "Trivial lower bound of the number of items = " << trivial_lb;
if (absl::GetFlag(FLAGS_max_bins) == 0) {
LOG(INFO) << "Setting max_bins to " << trivial_lb * 2;
}
LOG(INFO) << "Trivial lower bound of the number of bins = " << trivial_lb;
const int max_bins = absl::GetFlag(FLAGS_max_bins) == 0
? trivial_lb * 2
: absl::GetFlag(FLAGS_max_bins);
if (absl::GetFlag(FLAGS_max_bins) == 0) {
LOG(INFO) << "Setting max_bins to " << max_bins;
}
CpModelBuilder cp_model;
// Selects the right shape for each item (plus nil shape if not selected).
// The nil shape is the first choice.
std::vector<std::vector<BoolVar>> selected(num_items);
// We do not support multiple shapes per item.
for (int item = 0; item < num_items; ++item) {
const int num_shapes = problem.items(item).shapes_size();
CHECK_EQ(1, num_shapes);
selected[item].resize(max_bins);
}
// Create one Boolean variable per item and per bin.
std::vector<std::vector<BoolVar>> item_to_bin(num_items);
for (int item = 0; item < num_items; ++item) {
item_to_bin[item].resize(max_bins);
for (int b = 0; b < max_bins; ++b) {
selected[item][b] = cp_model.NewBoolVar();
item_to_bin[item][b] = cp_model.NewBoolVar();
}
}
// Exactly one bin is selected for each item.
for (int item = 0; item < num_items; ++item) {
cp_model.AddEquality(LinearExpr::Sum(selected[item]), 1);
cp_model.AddEquality(LinearExpr::Sum(item_to_bin[item]), 1);
}
// Manages positions and sizes for each item.
std::vector<std::vector<std::vector<IntervalVar>>> intervals(num_items);
std::vector<std::vector<std::vector<IntervalVar>>>
interval_by_item_bin_dimension(num_items);
for (int item = 0; item < num_items; ++item) {
intervals[item].resize(max_bins);
interval_by_item_bin_dimension[item].resize(max_bins);
for (int b = 0; b < max_bins; ++b) {
intervals[item][b].resize(2);
interval_by_item_bin_dimension[item][b].resize(2);
for (int dim = 0; dim < num_dimensions; ++dim) {
const int64_t dimension = box_dimensions[dim];
const int64_t size = problem.items(item).shapes(0).dimensions(dim);
IntVar start = cp_model.NewIntVar({0, dimension - size});
intervals[item][b][dim] = cp_model.NewOptionalFixedSizeIntervalVar(
start, size, selected[item][b]);
const IntVar start = cp_model.NewIntVar({0, dimension - size});
interval_by_item_bin_dimension[item][b][dim] =
cp_model.NewOptionalFixedSizeIntervalVar(start, size,
item_to_bin[item][b]);
}
}
}
@@ -120,8 +127,8 @@ void LoadAndSolve(const std::string& file_name, int instance) {
for (int b = 0; b < max_bins; ++b) {
NoOverlap2DConstraint no_overlap_2d = cp_model.AddNoOverlap2D();
for (int item = 0; item < num_items; ++item) {
no_overlap_2d.AddRectangle(intervals[item][b][0],
intervals[item][b][1]);
no_overlap_2d.AddRectangle(interval_by_item_bin_dimension[item][b][0],
interval_by_item_bin_dimension[item][b][1]);
}
}
} else {
@@ -131,42 +138,39 @@ void LoadAndSolve(const std::string& file_name, int instance) {
// Redundant constraint.
LinearExpr sum_of_areas;
for (int item = 0; item < num_items; ++item) {
const int item_area = problem.items(item).shapes(0).dimensions(0) *
problem.items(item).shapes(0).dimensions(1);
const int64_t item_area = problem.items(item).shapes(0).dimensions(0) *
problem.items(item).shapes(0).dimensions(1);
for (int b = 0; b < max_bins; ++b) {
sum_of_areas += selected[item][b] * item_area;
sum_of_areas += item_to_bin[item][b] * item_area;
}
}
cp_model.AddEquality(sum_of_areas, sum_of_items_area);
// Symmetry breaking: The number of items per bin is decreasing.
std::vector<LinearExpr> num_items_per_bins(max_bins);
LinearExpr all_items;
std::vector<LinearExpr> num_items_in_bin(max_bins);
for (int b = 0; b < max_bins; ++b) {
for (int item = 0; item < num_items; ++item) {
num_items_per_bins[b] += selected[item][b];
all_items += selected[item][b];
num_items_in_bin[b] += item_to_bin[item][b];
}
}
for (int b = 1; b < max_bins; ++b) {
cp_model.AddLessOrEqual(num_items_per_bins[b - 1], num_items_per_bins[b]);
cp_model.AddLessOrEqual(num_items_in_bin[b - 1], num_items_in_bin[b]);
}
cp_model.AddEquality(all_items, num_items);
// Objective.
std::vector<BoolVar> bin_is_selected(max_bins);
std::vector<BoolVar> bin_is_used(max_bins);
for (int b = 0; b < max_bins; ++b) {
bin_is_selected[b] = cp_model.NewBoolVar();
// Link bin_is_selected[i] with the items in bin i.
std::vector<BoolVar> all_items;
bin_is_used[b] = cp_model.NewBoolVar();
// Link bin_is_used[i] with the items in bin i.
std::vector<BoolVar> all_items_in_bin;
for (int item = 0; item < num_items; ++item) {
cp_model.AddImplication(selected[item][b], bin_is_selected[b]);
all_items.push_back(selected[item][b].Not());
cp_model.AddImplication(item_to_bin[item][b], bin_is_used[b]);
all_items_in_bin.push_back(item_to_bin[item][b].Not());
}
all_items.push_back(bin_is_selected[b].Not());
cp_model.AddBoolOr(all_items);
all_items_in_bin.push_back(bin_is_used[b].Not());
cp_model.AddBoolOr(all_items_in_bin);
}
cp_model.Minimize(LinearExpr::Sum(bin_is_selected));
cp_model.Minimize(LinearExpr::Sum(bin_is_used));
// Setup parameters.
SatParameters parameters;
@@ -177,7 +181,7 @@ void LoadAndSolve(const std::string& file_name, int instance) {
absl::GetFlag(FLAGS_params), &parameters))
<< absl::GetFlag(FLAGS_params);
}
// We rely on the solver default logging to log the number of bins.
const CpSolverResponse response =
SolveWithParameters(cp_model.Build(), parameters);
}

View File

@@ -44,7 +44,7 @@ namespace sat {
void CheckAndPrint2DSolution(
const CpSolverResponse& response,
const packing::MultipleDimensionsBinPackingProblem& problem,
const std::vector<std::vector<IntervalVar>>& intervals,
const std::vector<std::vector<IntervalVar>>& interval_by_item_dimension,
std::string* solution_in_ascii_form) {
const int num_items = problem.items_size();
@@ -59,17 +59,18 @@ void CheckAndPrint2DSolution(
}
int64_t used_area = 0;
for (int item = 0; item < num_items; ++item) {
if (!SolutionBooleanValue(response, intervals[item][0].PresenceBoolVar())) {
if (!SolutionBooleanValue(
response, interval_by_item_dimension[item][0].PresenceBoolVar())) {
continue;
}
const int64_t x =
SolutionIntegerValue(response, intervals[item][0].StartExpr());
const int64_t y =
SolutionIntegerValue(response, intervals[item][1].StartExpr());
const int64_t dx =
SolutionIntegerValue(response, intervals[item][0].SizeExpr());
const int64_t dy =
SolutionIntegerValue(response, intervals[item][1].SizeExpr());
const int64_t x = SolutionIntegerValue(
response, interval_by_item_dimension[item][0].StartExpr());
const int64_t y = SolutionIntegerValue(
response, interval_by_item_dimension[item][1].StartExpr());
const int64_t dx = SolutionIntegerValue(
response, interval_by_item_dimension[item][0].SizeExpr());
const int64_t dy = SolutionIntegerValue(
response, interval_by_item_dimension[item][1].SizeExpr());
used_area += dx * dy;
for (int i = x; i < x + dx; ++i) {
for (int j = y; j < y + dy; ++j) {
@@ -142,23 +143,25 @@ void LoadAndSolve(const std::string& file_name, int instance) {
}
// Manages positions and sizes for each item.
std::vector<std::vector<IntervalVar>> intervals(num_items);
std::vector<std::vector<IntervalVar>> interval_by_item_dimension(num_items);
for (int item = 0; item < num_items; ++item) {
intervals[item].resize(num_dimensions);
interval_by_item_dimension[item].resize(num_dimensions);
const int num_shapes = problem.items(item).shapes_size();
for (int dim = 0; dim < num_dimensions; ++dim) {
if (num_shapes == 1) {
const int64_t dimension = box_dimensions[dim];
const int64_t size = problem.items(item).shapes(0).dimensions(dim);
IntVar start = cp_model.NewIntVar({0, dimension - size});
intervals[item][dim] = cp_model.NewOptionalFixedSizeIntervalVar(
start, size, selected[item][1]);
interval_by_item_dimension[item][dim] =
cp_model.NewOptionalFixedSizeIntervalVar(start, size,
selected[item][1]);
} else {
const Domain dimension(0, box_dimensions[dim]);
IntVar start = cp_model.NewIntVar(dimension);
IntVar size = cp_model.NewIntVar(dimension);
IntVar end = cp_model.NewIntVar(dimension);
intervals[item][dim] = cp_model.NewIntervalVar(start, size, end);
const IntVar start = cp_model.NewIntVar(dimension);
const IntVar size = cp_model.NewIntVar(dimension);
const IntVar end = cp_model.NewIntVar(dimension);
interval_by_item_dimension[item][dim] =
cp_model.NewIntervalVar(start, size, end);
for (int shape = 0; shape <= num_shapes; ++shape) {
const int64_t item_size_in_dim =
@@ -179,7 +182,8 @@ void LoadAndSolve(const std::string& file_name, int instance) {
LOG(INFO) << "Box size: " << box_dimensions[0] << "*" << box_dimensions[1];
NoOverlap2DConstraint no_overlap_2d = cp_model.AddNoOverlap2D();
for (int item = 0; item < num_items; ++item) {
no_overlap_2d.AddRectangle(intervals[item][0], intervals[item][1]);
no_overlap_2d.AddRectangle(interval_by_item_dimension[item][0],
interval_by_item_dimension[item][1]);
}
} else {
LOG(FATAL) << num_dimensions << " dimensions not supported.";
@@ -206,12 +210,12 @@ void LoadAndSolve(const std::string& file_name, int instance) {
std::string solution_in_ascii_form;
model.Add(NewFeasibleSolutionObserver([&](const CpSolverResponse& r) {
if (num_dimensions == 2) {
CheckAndPrint2DSolution(r, problem, intervals, &solution_in_ascii_form);
CheckAndPrint2DSolution(r, problem, interval_by_item_dimension,
&solution_in_ascii_form);
}
}));
const CpSolverResponse response = SolveCpModel(cp_model.Build(), &model);
LOG(INFO) << CpSolverResponseStats(response);
if (!solution_in_ascii_form.empty()) {
LOG(INFO) << solution_in_ascii_form;