enrich arc-flow solver with optional items and cost per bin
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user