enrich arc-flow solver with optional items and cost per bin

This commit is contained in:
Laurent Perron
2022-12-24 15:06:02 +01:00
parent ee8b9e0989
commit 098dd7bb12
2 changed files with 53 additions and 20 deletions

View File

@@ -46,7 +46,8 @@ double ConvertVectorBinPackingProblem(const vbp::VectorBinPackingProblem& input,
for (int i = 0; i < num_items; ++i) {
shapes[i].assign(input.item(i).resource_usage().begin(),
input.item(i).resource_usage().end());
demands[i] = input.item(i).num_copies();
demands[i] =
input.item(i).num_copies() + input.item(i).num_optional_copies();
}
for (int i = 0; i < num_dims; ++i) {
capacities[i] = input.resource_capacity(i);
@@ -87,7 +88,7 @@ vbp::VectorBinPackingSolution SolveVectorBinPackingWithArcFlow(
max_num_bins = max_bins;
} else {
for (const auto& item : problem.item()) {
max_num_bins += item.num_copies();
max_num_bins += item.num_copies() + item.num_optional_copies();
}
}
const int num_types = problem.item_size();
@@ -98,6 +99,7 @@ vbp::VectorBinPackingSolution SolveVectorBinPackingWithArcFlow(
MPSolver solver("VectorBinPacking", solver_type);
CHECK_OK(solver.SetNumThreads(num_threads));
MPObjective* const objective = solver.MutableObjective();
for (int v = 0; v < graph.arcs.size(); ++v) {
const ArcFlowGraph::Arc& arc = graph.arcs[v];
@@ -113,9 +115,14 @@ vbp::VectorBinPackingSolution SolveVectorBinPackingWithArcFlow(
// Per item demand constraint.
for (int i = 0; i < num_types; ++i) {
MPConstraint* const ct = solver.MakeRowConstraint(
problem.item(i).num_copies(), problem.item(i).num_copies());
const vbp::Item& item = problem.item(i);
int max_copies = item.num_copies() + item.num_optional_copies();
MPConstraint* const ct =
solver.MakeRowConstraint(item.num_copies(), max_copies);
objective->SetOffset(max_copies * item.penalty_per_missing_copy() +
objective->offset());
for (MPVariable* const var : item_to_vars[i]) {
objective->SetCoefficient(var, -item.penalty_per_missing_copy());
ct->SetCoefficient(var, 1.0);
}
}
@@ -131,13 +138,16 @@ vbp::VectorBinPackingSolution SolveVectorBinPackingWithArcFlow(
}
}
MPVariable* const obj_var = solver.MakeIntVar(0, max_num_bins, "obj_var");
MPVariable* const num_bins_var =
solver.MakeIntVar(0, max_num_bins, "num_bins_var");
objective->SetCoefficient(
num_bins_var, problem.has_cost_per_bin() ? problem.cost_per_bin() : 1.0);
{ // Source.
MPConstraint* const ct = solver.MakeRowConstraint(0.0, 0.0);
ct->SetCoefficient(num_bins_var, 1.0);
for (MPVariable* const var : outgoing_vars[/*source*/ 0]) {
ct->SetCoefficient(var, 1.0);
ct->SetCoefficient(var, -1.0);
}
ct->SetCoefficient(obj_var, -1.0);
}
{ // Sink.
@@ -146,12 +156,9 @@ vbp::VectorBinPackingSolution SolveVectorBinPackingWithArcFlow(
for (MPVariable* const var : incoming_vars[sink_node]) {
ct->SetCoefficient(var, 1.0);
}
ct->SetCoefficient(obj_var, -1.0);
ct->SetCoefficient(num_bins_var, -1.0);
}
MPObjective* const objective = solver.MutableObjective();
objective->SetCoefficient(obj_var, 1.0);
if (!absl::GetFlag(FLAGS_arc_flow_dump_model).empty()) {
MPModelProto output_model;
solver.ExportModelToProto(&output_model);
@@ -207,23 +214,25 @@ vbp::VectorBinPackingSolution SolveVectorBinPackingWithArcFlow(
auto& [next, count, item] = node_to_next_count_item[node].back();
CHECK_NE(next, -1);
CHECK_GT(count, 0);
// Copy next and item before popping.
const NextItem result{next, item};
if (--count == 0) {
node_to_next_count_item[node].pop_back();
}
return NextItem({next, item});
return result;
};
VLOG(1) << "Packing used " << num_bins_var->solution_value()
<< " total bins";
const int start_node = 0;
const int end_node = graph.nodes.size() - 1;
while (!node_to_next_count_item[start_node].empty()) {
absl::btree_map<int, int> item_count;
int current = start_node;
while (current != end_node) {
const auto& [next, item] = pop_next_item(current);
const auto [next, item] = pop_next_item(current);
if (item != -1) {
item_count[item]++;
} else {
CHECK_EQ(next, end_node);
}
current = next;
}
@@ -233,6 +242,10 @@ vbp::VectorBinPackingSolution SolveVectorBinPackingWithArcFlow(
bin->add_item_copies(count);
}
}
CHECK_EQ(solution.bins_size(), std::round(num_bins_var->solution_value()));
for (const auto& next_counts : node_to_next_count_item) {
CHECK(next_counts.empty());
}
}
return solution;

View File

@@ -23,9 +23,10 @@
// copies for each item.
//
// The goal is either:
// - optimization: minimizing the number of bins needed to fit all items, or
// - feasibility: checking if all items can be packed using at most a given
// number of bins.
// - optimization: minimizing a weighted sum of the number of bins used, plus a
// weighted sum of skipped items; or
// - feasibility: checking if all required items can be packed using at most a
// given number of bins.
//
// In both cases we must ensure that for each bin and each resource, the sum of
// sizes of each assigned item is less than the capacity of the resource of the
@@ -51,13 +52,24 @@ message Item {
// VectorBinPackingProblem.
repeated int64 resource_usage = 2;
// Number of identical copies of this item.
// Number of identical copies of this item that must be packed into some bin.
int32 num_copies = 3;
// The number of extra copies which may be skipped for a penalty.
// Currently only supported by the ArcFlow solver (arc_flow_solver.h), other
// solvers ignore this field.
int32 num_optional_copies = 5;
// An optional upper bound on how many copies of the same item are allowed in
// a single bin, regardless of resource utilization. A value of 0 is
// interpreted as no limit.
int32 max_number_of_copies_per_bin = 4;
// Minimize the total cost of bins plus this penalty for each optional copy
// not placed in any bin.
// Currently only supported by the ArcFlow solver (arc_flow_solver.h), other
// solvers ignore this field.
double penalty_per_missing_copy = 6;
}
message VectorBinPackingProblem {
@@ -76,8 +88,15 @@ message VectorBinPackingProblem {
repeated Item item = 4;
// The maximum number of bins available. A value of 0 is interpreted as no
// limit. Nonzero values are used to encode feasibility problems.
// limit. Nonzero values may be used to encode feasibility problems.
int32 max_bins = 5;
// If specified, tries to maximize the value of packed items minus the cost
// per bin used. A missing value is treated as 1.
// Currently only supported by the ArcFlow solver
// (ortools/packing/arc_flow_solver.h), other solvers
// ignore this field.
optional double cost_per_bin = 6;
}
// Describe one filled bin in the solution.
@@ -114,6 +133,7 @@ message VectorBinPackingSolution {
VectorBinPackingSolveStatus status = 3;
// Objective value.
// The total cost of bins used plus the penalty for any skipped items.
double objective_value = 4;
// Solve time in seconds.