2022-06-17 08:40:20 +02:00
|
|
|
// Copyright 2010-2022 Google LLC
|
2021-12-18 18:16:52 +01:00
|
|
|
// 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.
|
|
|
|
|
// It loads the size of the main rectangle, all available items (rectangles
|
|
|
|
|
// too), and tries to fit all rectangles in the minimum numbers of bins (they
|
|
|
|
|
// have the size of the main rectangle.)
|
|
|
|
|
|
2023-10-24 17:40:38 +02:00
|
|
|
#include <algorithm>
|
2021-12-18 18:16:52 +01:00
|
|
|
#include <cstdint>
|
|
|
|
|
#include <string>
|
2023-10-26 11:00:54 +02:00
|
|
|
#include <string_view>
|
2021-12-18 18:16:52 +01:00
|
|
|
#include <vector>
|
|
|
|
|
|
2023-10-25 17:05:47 +02:00
|
|
|
#include "absl/container/btree_set.h"
|
2021-12-18 18:16:52 +01:00
|
|
|
#include "absl/flags/flag.h"
|
2023-08-24 17:14:58 +02:00
|
|
|
#include "absl/log/check.h"
|
2023-10-24 17:40:38 +02:00
|
|
|
#include "absl/strings/str_cat.h"
|
2021-12-18 18:16:52 +01:00
|
|
|
#include "google/protobuf/text_format.h"
|
2022-02-25 09:47:52 +01:00
|
|
|
#include "ortools/base/init_google.h"
|
2021-12-18 18:16:52 +01:00
|
|
|
#include "ortools/base/logging.h"
|
2023-10-24 17:40:38 +02:00
|
|
|
#include "ortools/base/path.h"
|
2021-12-18 18:16:52 +01:00
|
|
|
#include "ortools/packing/binpacking_2d_parser.h"
|
|
|
|
|
#include "ortools/packing/multiple_dimensions_bin_packing.pb.h"
|
|
|
|
|
#include "ortools/sat/cp_model.h"
|
2023-08-24 17:14:58 +02:00
|
|
|
#include "ortools/sat/cp_model.pb.h"
|
|
|
|
|
#include "ortools/sat/cp_model_solver.h"
|
|
|
|
|
#include "ortools/sat/sat_parameters.pb.h"
|
|
|
|
|
#include "ortools/sat/util.h"
|
2021-12-18 18:16:52 +01:00
|
|
|
|
|
|
|
|
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.");
|
|
|
|
|
ABSL_FLAG(int, max_bins, 0,
|
|
|
|
|
"Maximum number of bins. The 0 default value implies the code will "
|
|
|
|
|
"use some heuristics to compute this number.");
|
2022-02-25 09:47:52 +01:00
|
|
|
ABSL_FLAG(bool, symmetry_breaking, true, "Use symmetry breaking constraints");
|
2023-10-24 17:40:38 +02:00
|
|
|
ABSL_FLAG(bool, use_global_cumulative, true,
|
|
|
|
|
"Use a globalcumulative relaxation");
|
2021-12-18 18:16:52 +01:00
|
|
|
|
|
|
|
|
namespace operations_research {
|
|
|
|
|
namespace sat {
|
|
|
|
|
|
2023-10-26 11:00:54 +02:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
class GreaterByArea {
|
|
|
|
|
public:
|
|
|
|
|
explicit GreaterByArea(
|
|
|
|
|
const packing::MultipleDimensionsBinPackingProblem& problem)
|
|
|
|
|
: problem_(problem) {}
|
|
|
|
|
|
|
|
|
|
bool operator()(int a, int b) const {
|
|
|
|
|
const auto& a_dims = problem_.items(a).shapes(0).dimensions();
|
|
|
|
|
const auto& b_dims = problem_.items(b).shapes(0).dimensions();
|
|
|
|
|
|
|
|
|
|
return a_dims.Get(0) * a_dims.Get(1) > b_dims.Get(0) * b_dims.Get(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
const packing::MultipleDimensionsBinPackingProblem& problem_;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bool ItemsAreIncompatible(
|
|
|
|
|
const packing::MultipleDimensionsBinPackingProblem& problem, int i1,
|
|
|
|
|
int i2) {
|
|
|
|
|
const auto& box_dimensions = problem.box_shape().dimensions();
|
|
|
|
|
const auto& i1_dims = problem.items(i1).shapes(0).dimensions();
|
|
|
|
|
const auto& i2_dims = problem.items(i2).shapes(0).dimensions();
|
|
|
|
|
|
|
|
|
|
return (i1_dims.Get(0) + i2_dims.Get(0) > box_dimensions[0]) &&
|
|
|
|
|
(i1_dims.Get(1) + i2_dims.Get(1) > box_dimensions[1]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
absl::btree_set<int> FindFixedItems(
|
|
|
|
|
const packing::MultipleDimensionsBinPackingProblem& problem) {
|
|
|
|
|
absl::btree_set<int> fixed_items;
|
|
|
|
|
|
|
|
|
|
// We start by fixing big pairwise incompatible items. Each to its own bin.
|
|
|
|
|
// See https://arxiv.org/pdf/1909.06835.pdf.
|
|
|
|
|
const int num_items = problem.items_size();
|
|
|
|
|
const auto box_dimensions = problem.box_shape().dimensions();
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now we fixed all items that are too big to fit any two of them in a bin.
|
|
|
|
|
// There could still be two items that are incompatible with all the big ones
|
|
|
|
|
// and one with one another: a very wide one and a very tall one. Let's fix
|
|
|
|
|
// those two too if they exist. Note that if there are no big items
|
|
|
|
|
// incompatible_pair_candidates contains all items and we will fix the first
|
|
|
|
|
// pairwise incompatible pair.
|
|
|
|
|
absl::btree_set<int> incompatible_pair_candidates;
|
|
|
|
|
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 (!ItemsAreIncompatible(problem, item, i)) {
|
|
|
|
|
incompatible_with_all = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (incompatible_with_all) {
|
|
|
|
|
incompatible_pair_candidates.insert(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bool found_incompatible_pair = false;
|
|
|
|
|
for (const int i1 : incompatible_pair_candidates) {
|
|
|
|
|
for (const int i2 : incompatible_pair_candidates) {
|
|
|
|
|
if (i1 == i2) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (ItemsAreIncompatible(problem, i1, i2)) {
|
|
|
|
|
// We found a pair that is incompatible with all the big items and
|
|
|
|
|
// between one another.
|
|
|
|
|
fixed_items.insert(i1);
|
|
|
|
|
fixed_items.insert(i2);
|
|
|
|
|
found_incompatible_pair = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (found_incompatible_pair) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!found_incompatible_pair && !incompatible_pair_candidates.empty()) {
|
|
|
|
|
// We could not add a pair of mutually incompatible items to our list. But
|
|
|
|
|
// we know a set of elements that are incompatible with all the big ones.
|
|
|
|
|
// Let's add the one with the largest area.
|
|
|
|
|
fixed_items.insert(*std::min_element(incompatible_pair_candidates.begin(),
|
|
|
|
|
incompatible_pair_candidates.end(),
|
|
|
|
|
GreaterByArea(problem)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!fixed_items.empty()) {
|
|
|
|
|
std::string_view message_end = ".";
|
|
|
|
|
if (found_incompatible_pair) {
|
|
|
|
|
message_end =
|
|
|
|
|
" (including the extra two that are big in only one "
|
|
|
|
|
"dimensions).";
|
|
|
|
|
} else if (!incompatible_pair_candidates.empty()) {
|
|
|
|
|
message_end =
|
|
|
|
|
" (including an extra one that is incompatible with all big ones).";
|
|
|
|
|
}
|
|
|
|
|
LOG(INFO) << fixed_items.size() << " items are pairwise incompatible"
|
|
|
|
|
<< message_end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fixed_items.empty()) {
|
|
|
|
|
// We couldn't fix any items, just fix the one with the biggest area.
|
|
|
|
|
std::vector<int> all_items;
|
|
|
|
|
for (int i = 0; i < num_items; ++i) {
|
|
|
|
|
all_items.push_back(i);
|
|
|
|
|
}
|
|
|
|
|
fixed_items.insert(*std::min_element(all_items.begin(), all_items.end(),
|
|
|
|
|
GreaterByArea(problem)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fixed_items;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2023-06-21 17:31:06 +02:00
|
|
|
// Load a 2D bin packing problem and solve it.
|
2021-12-18 18:16:52 +01:00
|
|
|
void LoadAndSolve(const std::string& file_name, int instance) {
|
|
|
|
|
packing::BinPacking2dParser parser;
|
|
|
|
|
if (!parser.Load2BPFile(file_name, instance)) {
|
2021-12-22 09:53:33 +01:00
|
|
|
LOG(FATAL) << "Cannot read instance " << instance << " from file '"
|
|
|
|
|
<< file_name << "'";
|
2021-12-18 18:16:52 +01:00
|
|
|
}
|
|
|
|
|
packing::MultipleDimensionsBinPackingProblem problem = parser.problem();
|
2021-12-22 09:53:33 +01:00
|
|
|
LOG(INFO) << "Successfully loaded instance " << instance << " from file '"
|
|
|
|
|
<< file_name << "'";
|
2021-12-18 18:16:52 +01:00
|
|
|
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();
|
|
|
|
|
|
2023-10-24 17:40:38 +02:00
|
|
|
// Non overlapping.
|
|
|
|
|
if (num_dimensions == 1) {
|
|
|
|
|
LOG(FATAL) << "One dimension is not supported.";
|
|
|
|
|
} else if (num_dimensions != 2) {
|
|
|
|
|
LOG(FATAL) << num_dimensions << " dimensions not supported.";
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-22 09:53:33 +01:00
|
|
|
const int64_t area_of_one_bin = box_dimensions[0] * box_dimensions[1];
|
|
|
|
|
int64_t sum_of_items_area = 0;
|
2021-12-18 18:16:52 +01:00
|
|
|
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);
|
|
|
|
|
}
|
2021-12-22 09:53:33 +01:00
|
|
|
|
2023-08-24 17:14:58 +02:00
|
|
|
const int64_t trivial_lb = CeilOfRatio(sum_of_items_area, area_of_one_bin);
|
2021-12-22 09:53:33 +01:00
|
|
|
LOG(INFO) << "Trivial lower bound of the number of bins = " << trivial_lb;
|
2021-12-18 18:16:52 +01:00
|
|
|
const int max_bins = absl::GetFlag(FLAGS_max_bins) == 0
|
|
|
|
|
? trivial_lb * 2
|
|
|
|
|
: absl::GetFlag(FLAGS_max_bins);
|
2021-12-22 09:53:33 +01:00
|
|
|
if (absl::GetFlag(FLAGS_max_bins) == 0) {
|
|
|
|
|
LOG(INFO) << "Setting max_bins to " << max_bins;
|
|
|
|
|
}
|
2021-12-18 18:16:52 +01:00
|
|
|
|
|
|
|
|
CpModelBuilder cp_model;
|
2023-10-24 17:40:38 +02:00
|
|
|
cp_model.SetName(absl::StrCat(
|
|
|
|
|
"binpacking_2d_", file::Stem(absl::GetFlag(FLAGS_input)), "_", instance));
|
2021-12-18 18:16:52 +01:00
|
|
|
|
2021-12-22 09:53:33 +01:00
|
|
|
// We do not support multiple shapes per item.
|
2021-12-18 18:16:52 +01:00
|
|
|
for (int item = 0; item < num_items; ++item) {
|
|
|
|
|
const int num_shapes = problem.items(item).shapes_size();
|
|
|
|
|
CHECK_EQ(1, num_shapes);
|
2021-12-22 09:53:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|
2021-12-18 18:16:52 +01:00
|
|
|
for (int b = 0; b < max_bins; ++b) {
|
2021-12-22 09:53:33 +01:00
|
|
|
item_to_bin[item][b] = cp_model.NewBoolVar();
|
2021-12-18 18:16:52 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Exactly one bin is selected for each item.
|
|
|
|
|
for (int item = 0; item < num_items; ++item) {
|
2023-08-24 17:14:58 +02:00
|
|
|
cp_model.AddExactlyOne(item_to_bin[item]);
|
2021-12-18 18:16:52 +01:00
|
|
|
}
|
|
|
|
|
|
2023-10-26 11:00:54 +02:00
|
|
|
const absl::btree_set<int> fixed_items = FindFixedItems(problem);
|
2023-10-25 17:05:47 +02:00
|
|
|
|
2023-10-26 11:00:54 +02:00
|
|
|
// 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;
|
2023-10-25 17:05:47 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-24 17:40:38 +02:00
|
|
|
// 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) {
|
2023-10-25 17:05:47 +02:00
|
|
|
if (fixed_items.contains(i1) && fixed_items.contains(i2)) {
|
|
|
|
|
// Both are already fixed to different bins.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2023-10-26 11:00:54 +02:00
|
|
|
if (!ItemsAreIncompatible(problem, i1, i2)) {
|
2023-10-25 17:05:47 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
num_incompatible_pairs++;
|
|
|
|
|
for (int b = 0; b < max_bins; ++b) {
|
|
|
|
|
cp_model.AddAtMostOne({item_to_bin[i1][b], item_to_bin[i2][b]});
|
2023-10-24 17:40:38 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (num_incompatible_pairs > 0) {
|
|
|
|
|
LOG(INFO) << num_incompatible_pairs << " incompatible pairs of items";
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-18 18:16:52 +01:00
|
|
|
// Manages positions and sizes for each item.
|
2021-12-22 09:53:33 +01:00
|
|
|
std::vector<std::vector<std::vector<IntervalVar>>>
|
|
|
|
|
interval_by_item_bin_dimension(num_items);
|
2023-10-24 17:40:38 +02:00
|
|
|
std::vector<std::vector<IntVar>> starts_by_dimension(num_items);
|
2021-12-18 18:16:52 +01:00
|
|
|
for (int item = 0; item < num_items; ++item) {
|
2021-12-22 09:53:33 +01:00
|
|
|
interval_by_item_bin_dimension[item].resize(max_bins);
|
2023-10-24 17:40:38 +02:00
|
|
|
starts_by_dimension[item].resize(num_dimensions);
|
2021-12-18 18:16:52 +01:00
|
|
|
for (int b = 0; b < max_bins; ++b) {
|
2023-10-24 17:40:38 +02:00
|
|
|
interval_by_item_bin_dimension[item][b].resize(num_dimensions);
|
2021-12-18 18:16:52 +01:00
|
|
|
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);
|
2023-10-24 17:40:38 +02:00
|
|
|
IntVar start;
|
|
|
|
|
if (b == 0) {
|
|
|
|
|
start = cp_model.NewIntVar({0, dimension - size});
|
|
|
|
|
starts_by_dimension[item][dim] = start;
|
|
|
|
|
} else {
|
|
|
|
|
start = starts_by_dimension[item][dim];
|
|
|
|
|
}
|
2021-12-22 09:53:33 +01:00
|
|
|
interval_by_item_bin_dimension[item][b][dim] =
|
|
|
|
|
cp_model.NewOptionalFixedSizeIntervalVar(start, size,
|
|
|
|
|
item_to_bin[item][b]);
|
2021-12-18 18:16:52 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Non overlapping.
|
2023-10-24 17:40:38 +02:00
|
|
|
LOG(INFO) << "Box size: " << box_dimensions[0] << "*" << box_dimensions[1];
|
|
|
|
|
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(interval_by_item_bin_dimension[item][b][0],
|
|
|
|
|
interval_by_item_bin_dimension[item][b][1]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Objective variable.
|
|
|
|
|
const IntVar obj = cp_model.NewIntVar({trivial_lb, max_bins});
|
|
|
|
|
|
|
|
|
|
// Global cumulative.
|
|
|
|
|
if (absl::GetFlag(FLAGS_use_global_cumulative)) {
|
|
|
|
|
DCHECK_EQ(num_dimensions, 2);
|
|
|
|
|
for (int dim = 0; dim < num_dimensions; ++dim) {
|
|
|
|
|
const int other_size = box_dimensions[1 - dim];
|
|
|
|
|
CumulativeConstraint cumul = cp_model.AddCumulative(obj * other_size);
|
2021-12-18 18:16:52 +01:00
|
|
|
for (int item = 0; item < num_items; ++item) {
|
2023-10-24 17:40:38 +02:00
|
|
|
const int size = problem.items(item).shapes(0).dimensions(dim);
|
|
|
|
|
const int demand = problem.items(item).shapes(0).dimensions(1 - dim);
|
|
|
|
|
cumul.AddDemand(cp_model.NewFixedSizeIntervalVar(
|
|
|
|
|
starts_by_dimension[item][dim], size),
|
|
|
|
|
demand);
|
2021-12-18 18:16:52 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-24 17:14:58 +02:00
|
|
|
// Maintain one Boolean variable per bin that indicates if the bin is used
|
|
|
|
|
// or not.
|
|
|
|
|
std::vector<BoolVar> bin_is_used(max_bins);
|
|
|
|
|
for (int b = 0; b < max_bins; ++b) {
|
|
|
|
|
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(item_to_bin[item][b], bin_is_used[b]);
|
|
|
|
|
all_items_in_bin.push_back(item_to_bin[item][b]);
|
2021-12-30 18:59:09 +01:00
|
|
|
}
|
2023-08-24 17:14:58 +02:00
|
|
|
cp_model.AddBoolOr(all_items_in_bin).OnlyEnforceIf(bin_is_used[b]);
|
|
|
|
|
}
|
2022-01-08 14:45:54 +01:00
|
|
|
|
2023-10-24 17:40:38 +02:00
|
|
|
// Objective definition.
|
2023-08-24 17:14:58 +02:00
|
|
|
cp_model.Minimize(obj);
|
|
|
|
|
for (int b = trivial_lb; b + 1 < max_bins; ++b) {
|
|
|
|
|
cp_model.AddGreaterOrEqual(obj, b + 1).OnlyEnforceIf(bin_is_used[b]);
|
|
|
|
|
cp_model.AddImplication(bin_is_used[b + 1], bin_is_used[b]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (absl::GetFlag(FLAGS_symmetry_breaking)) {
|
2023-10-25 17:05:47 +02:00
|
|
|
// First sort the items not yet fixed by area.
|
|
|
|
|
std::vector<int> 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(),
|
2023-10-26 11:00:54 +02:00
|
|
|
GreaterByArea(problem));
|
2023-10-25 17:05:47 +02:00
|
|
|
|
|
|
|
|
// 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);
|
2022-01-08 14:45:54 +01:00
|
|
|
}
|
2023-10-25 17:05:47 +02:00
|
|
|
++first_empty_bin;
|
2021-12-30 18:59:09 +01:00
|
|
|
}
|
2022-01-08 14:45:54 +01:00
|
|
|
}
|
2021-12-18 18:16:52 +01:00
|
|
|
|
|
|
|
|
// Setup parameters.
|
|
|
|
|
SatParameters parameters;
|
|
|
|
|
parameters.set_log_search_progress(true);
|
2022-03-25 10:07:27 +01:00
|
|
|
|
2021-12-18 18:16:52 +01:00
|
|
|
// Parse the --params flag.
|
|
|
|
|
if (!absl::GetFlag(FLAGS_params).empty()) {
|
|
|
|
|
CHECK(google::protobuf::TextFormat::MergeFromString(
|
|
|
|
|
absl::GetFlag(FLAGS_params), ¶meters))
|
|
|
|
|
<< absl::GetFlag(FLAGS_params);
|
|
|
|
|
}
|
2023-08-24 17:14:58 +02:00
|
|
|
|
|
|
|
|
// If number of workers is >= 16 and < 24, we prefer replacing
|
|
|
|
|
// objective_lb_search by objective_shaving_search.
|
|
|
|
|
if (parameters.num_workers() >= 16 && parameters.num_workers() < 24) {
|
|
|
|
|
parameters.add_ignore_subsolvers("objective_lb_search");
|
|
|
|
|
parameters.add_extra_subsolvers("objective_shaving_search");
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-22 09:53:33 +01:00
|
|
|
// We rely on the solver default logging to log the number of bins.
|
2021-12-18 18:16:52 +01:00
|
|
|
const CpSolverResponse response =
|
|
|
|
|
SolveWithParameters(cp_model.Build(), parameters);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace sat
|
|
|
|
|
} // namespace operations_research
|
|
|
|
|
|
|
|
|
|
int main(int argc, char** argv) {
|
2023-02-17 15:17:12 +01:00
|
|
|
absl::SetFlag(&FLAGS_stderrthreshold, 0);
|
2022-02-25 09:47:52 +01:00
|
|
|
InitGoogle(argv[0], &argc, &argv, true);
|
2021-12-18 18:16:52 +01:00
|
|
|
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;
|
|
|
|
|
}
|