243 lines
9.1 KiB
C++
243 lines
9.1 KiB
C++
// Copyright 2010-2021 Google LLC
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// This file solves a 2D Bin Packing problem as a 2D Knapsack problem.
|
|
// It loads the size of the mainrectangle, all available items (rectangles too),
|
|
// and tries to fit as many rectangles as possible in the main rectangle.
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "absl/flags/flag.h"
|
|
#include "absl/flags/parse.h"
|
|
#include "absl/flags/usage.h"
|
|
#include "google/protobuf/text_format.h"
|
|
#include "ortools/base/commandlineflags.h"
|
|
#include "ortools/base/logging.h"
|
|
#include "ortools/packing/binpacking_2d_parser.h"
|
|
#include "ortools/packing/multiple_dimensions_bin_packing.pb.h"
|
|
#include "ortools/sat/cp_model.h"
|
|
#include "ortools/sat/cp_model_solver.h"
|
|
|
|
ABSL_FLAG(std::string, input, "", "Input file.");
|
|
ABSL_FLAG(int, instance, -1, "Instance number if the file.");
|
|
ABSL_FLAG(std::string, params, "", "Sat parameters in text proto format.");
|
|
|
|
namespace operations_research {
|
|
namespace sat {
|
|
|
|
// Logs the current cost and fills solution_in_ascii_form with a nice ascii
|
|
// drawing of the current solution.
|
|
void CheckAndPrint2DSolution(
|
|
const CpSolverResponse& response,
|
|
const packing::MultipleDimensionsBinPackingProblem& problem,
|
|
const std::vector<std::vector<IntervalVar>>& interval_by_item_dimension,
|
|
std::string* solution_in_ascii_form) {
|
|
const int num_items = problem.items_size();
|
|
|
|
const int64_t objective = response.objective_value();
|
|
|
|
const int size_x = problem.box_shape().dimensions(0);
|
|
const int size_y = problem.box_shape().dimensions(1);
|
|
const int64_t box_area = size_x * size_y;
|
|
std::vector<std::vector<int>> values(size_y);
|
|
for (int i = 0; i < size_y; ++i) {
|
|
values[i].resize(size_x, -1);
|
|
}
|
|
int64_t used_area = 0;
|
|
for (int item = 0; item < num_items; ++item) {
|
|
if (!SolutionBooleanValue(
|
|
response, interval_by_item_dimension[item][0].PresenceBoolVar())) {
|
|
continue;
|
|
}
|
|
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) {
|
|
if (i >= size_x || j >= size_y) {
|
|
LOG(WARNING) << "Out of shape box: item = " << item << ", x = " << x
|
|
<< ", y = " << y << ", dx = " << dx << ", dy = " << dy;
|
|
} else {
|
|
if (values[i][j] != -1) {
|
|
LOG(WARNING) << "Item " << item << " overlaps with item "
|
|
<< values[i][j];
|
|
}
|
|
values[i][j] = item;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (num_items - objective <= 1) {
|
|
LOG(INFO) << "Cost " << objective << ", " << num_items - objective
|
|
<< " item selected, area used: " << used_area << "/" << box_area;
|
|
} else {
|
|
LOG(INFO) << "Cost " << objective << ", " << num_items - objective
|
|
<< " items selected, area used: " << used_area << "/" << box_area;
|
|
}
|
|
|
|
solution_in_ascii_form->clear();
|
|
for (int j = 0; j < size_y; ++j) {
|
|
for (int i = 0; i < size_x; ++i) {
|
|
const int v = values[i][j];
|
|
(*solution_in_ascii_form) += v == -1 ? ' ' : 'A' + v;
|
|
}
|
|
solution_in_ascii_form->append("\n");
|
|
}
|
|
}
|
|
|
|
// Load a 2d binpacking problem and solve it as a 2d knapsack problem.
|
|
// That is fit the max number of object in one box.
|
|
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;
|
|
}
|
|
packing::MultipleDimensionsBinPackingProblem problem = parser.problem();
|
|
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();
|
|
|
|
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);
|
|
for (int item = 0; item < num_items; ++item) {
|
|
const int num_shapes = problem.items(item).shapes_size();
|
|
LOG(INFO) << " - item " << item << " has " << num_shapes << " shapes";
|
|
selected[item].resize(num_shapes + 1);
|
|
for (int shape = 0; shape <= num_shapes; ++shape) {
|
|
selected[item][shape] = cp_model.NewBoolVar();
|
|
}
|
|
}
|
|
|
|
// Exactly one shape is selected for each item.
|
|
for (int item = 0; item < num_items; ++item) {
|
|
cp_model.AddEquality(LinearExpr::Sum(selected[item]), 1);
|
|
}
|
|
|
|
// Manages positions and sizes for each item.
|
|
std::vector<std::vector<IntervalVar>> interval_by_item_dimension(num_items);
|
|
for (int item = 0; item < num_items; ++item) {
|
|
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});
|
|
interval_by_item_dimension[item][dim] =
|
|
cp_model.NewOptionalFixedSizeIntervalVar(start, size,
|
|
selected[item][1]);
|
|
} else {
|
|
const Domain dimension(0, box_dimensions[dim]);
|
|
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 =
|
|
shape == 0
|
|
? 0
|
|
: problem.items(item).shapes(shape - 1).dimensions(dim);
|
|
cp_model.AddEquality(size, item_size_in_dim)
|
|
.OnlyEnforceIf(selected[item][shape]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Non overlapping.
|
|
if (num_dimensions == 1) {
|
|
LOG(FATAL) << "One dimension is not supported.";
|
|
} else if (num_dimensions == 2) {
|
|
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(interval_by_item_dimension[item][0],
|
|
interval_by_item_dimension[item][1]);
|
|
}
|
|
} else {
|
|
LOG(FATAL) << num_dimensions << " dimensions not supported.";
|
|
}
|
|
|
|
// Objective.
|
|
LinearExpr objective;
|
|
for (int item_id = 0; item_id < num_items; ++item_id) {
|
|
objective += selected[item_id][0] * problem.items(item_id).value();
|
|
}
|
|
cp_model.Minimize(objective);
|
|
|
|
Model model;
|
|
// Setup parameters.
|
|
SatParameters parameters;
|
|
parameters.set_log_search_progress(true);
|
|
// Parse the --params flag.
|
|
if (!absl::GetFlag(FLAGS_params).empty()) {
|
|
CHECK(google::protobuf::TextFormat::MergeFromString(
|
|
absl::GetFlag(FLAGS_params), ¶meters))
|
|
<< absl::GetFlag(FLAGS_params);
|
|
}
|
|
model.Add(NewSatParameters(parameters));
|
|
std::string solution_in_ascii_form;
|
|
model.Add(NewFeasibleSolutionObserver([&](const CpSolverResponse& r) {
|
|
if (num_dimensions == 2) {
|
|
CheckAndPrint2DSolution(r, problem, interval_by_item_dimension,
|
|
&solution_in_ascii_form);
|
|
}
|
|
}));
|
|
|
|
const CpSolverResponse response = SolveCpModel(cp_model.Build(), &model);
|
|
|
|
if (!solution_in_ascii_form.empty()) {
|
|
LOG(INFO) << solution_in_ascii_form;
|
|
}
|
|
}
|
|
|
|
} // namespace sat
|
|
} // namespace operations_research
|
|
|
|
int main(int argc, char** argv) {
|
|
absl::SetFlag(&FLAGS_logtostderr, true);
|
|
google::InitGoogleLogging(argv[0]);
|
|
absl::ParseCommandLine(argc, argv);
|
|
if (absl::GetFlag(FLAGS_input).empty()) {
|
|
LOG(FATAL) << "Please supply a data file with --input=";
|
|
}
|
|
if (absl::GetFlag(FLAGS_instance) == -1) {
|
|
LOG(FATAL) << "Please supply a valid instance number with --instance=";
|
|
}
|
|
|
|
operations_research::sat::LoadAndSolve(absl::GetFlag(FLAGS_input),
|
|
absl::GetFlag(FLAGS_instance));
|
|
return EXIT_SUCCESS;
|
|
}
|