diff --git a/ortools/packing/arc_flow_solver.cc b/ortools/packing/arc_flow_solver.cc index ec08f9a3f0..39674bb894 100644 --- a/ortools/packing/arc_flow_solver.cc +++ b/ortools/packing/arc_flow_solver.cc @@ -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 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; diff --git a/ortools/packing/vector_bin_packing.proto b/ortools/packing/vector_bin_packing.proto index ba28fa5c43..580604c98c 100644 --- a/ortools/packing/vector_bin_packing.proto +++ b/ortools/packing/vector_bin_packing.proto @@ -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.