Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
arc_flow_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <cmath>
17#include <string>
18#include <vector>
19
20#include "absl/container/btree_map.h"
21#include "absl/flags/flag.h"
24#include "ortools/base/timer.h"
26#include "ortools/packing/vector_bin_packing.pb.h"
27
28ABSL_FLAG(std::string, arc_flow_dump_model, "",
29 "File to store the solver specific optimization proto.");
30
31namespace operations_research {
32namespace packing {
33
34namespace {
35double ConvertVectorBinPackingProblem(const vbp::VectorBinPackingProblem& input,
36 ArcFlowGraph* graph) {
37 WallTimer timer;
38 timer.Start();
39 const int num_items = input.item_size();
40 const int num_dims = input.resource_capacity_size();
41
42 // Collect problem data.
43 std::vector<std::vector<int>> shapes(num_items);
44 std::vector<int> demands(num_items);
45 std::vector<int> capacities(num_dims);
46 for (int i = 0; i < num_items; ++i) {
47 shapes[i].assign(input.item(i).resource_usage().begin(),
48 input.item(i).resource_usage().end());
49 demands[i] =
50 input.item(i).num_copies() + input.item(i).num_optional_copies();
51 }
52 for (int i = 0; i < num_dims; ++i) {
53 capacities[i] = input.resource_capacity(i);
54 }
55
56 // Add extra dimensions to encode max_number_of_copies_per_bin.
57 for (int i = 0; i < num_items; ++i) {
58 const int max_copies = input.item(i).max_number_of_copies_per_bin();
59 if (max_copies == 0 || max_copies >= demands[i]) continue;
60 capacities.push_back(max_copies);
61 for (int j = 0; j < num_items; ++j) {
62 shapes[j].push_back(i == j);
63 }
64 }
65
66 *graph = BuildArcFlowGraph(capacities, shapes, demands);
67 const double arc_flow_time = timer.Get();
68
69 VLOG(1) << "The arc-flow grah has " << graph->nodes.size() << " nodes, and "
70 << graph->arcs.size() << " arcs. It was created by exploring "
71 << graph->num_dp_states
72 << " states in the dynamic programming phase in " << arc_flow_time
73 << " s";
74 return arc_flow_time;
75}
76} // namespace
77
78vbp::VectorBinPackingSolution SolveVectorBinPackingWithArcFlow(
79 const vbp::VectorBinPackingProblem& problem,
81 const std::string& mip_params, double time_limit, int num_threads,
82 int max_bins) {
84 const double arc_flow_time = ConvertVectorBinPackingProblem(problem, &graph);
85
86 int max_num_bins = 0;
87 if (max_bins > 0) {
88 max_num_bins = max_bins;
89 } else {
90 for (const auto& item : problem.item()) {
91 max_num_bins += item.num_copies() + item.num_optional_copies();
92 }
93 }
94 const int num_types = problem.item_size();
95 std::vector<std::vector<MPVariable*>> incoming_vars(graph.nodes.size());
96 std::vector<std::vector<MPVariable*>> outgoing_vars(graph.nodes.size());
97 std::vector<MPVariable*> arc_to_var(graph.arcs.size());
98 std::vector<std::vector<MPVariable*>> item_to_vars(num_types);
99
100 MPSolver solver("VectorBinPacking", solver_type);
101 CHECK_OK(solver.SetNumThreads(num_threads));
102 MPObjective* const objective = solver.MutableObjective();
103
104 for (int v = 0; v < graph.arcs.size(); ++v) {
105 const ArcFlowGraph::Arc& arc = graph.arcs[v];
106 MPVariable* const var =
107 solver.MakeIntVar(0, max_num_bins, absl::StrCat("a", v));
108 incoming_vars[arc.destination].push_back(var);
109 outgoing_vars[arc.source].push_back(var);
110 if (arc.item_index != -1) {
111 item_to_vars[arc.item_index].push_back(var);
112 }
113 arc_to_var[v] = var;
114 }
115
116 // Per item demand constraint.
117 for (int i = 0; i < num_types; ++i) {
118 const vbp::Item& item = problem.item(i);
119 int max_copies = item.num_copies() + item.num_optional_copies();
120 MPConstraint* const ct =
121 solver.MakeRowConstraint(item.num_copies(), max_copies);
122 objective->SetOffset(max_copies * item.penalty_per_missing_copy() +
123 objective->offset());
124 for (MPVariable* const var : item_to_vars[i]) {
125 objective->SetCoefficient(var, -item.penalty_per_missing_copy());
126 ct->SetCoefficient(var, 1.0);
127 }
128 }
129
130 // Flow conservation.
131 for (int n = 1; n < graph.nodes.size() - 1; ++n) { // Ignore source and sink.
132 MPConstraint* const ct = solver.MakeRowConstraint(0.0, 0.0);
133 for (MPVariable* const var : incoming_vars[n]) {
134 ct->SetCoefficient(var, 1.0);
135 }
136 for (MPVariable* const var : outgoing_vars[n]) {
137 ct->SetCoefficient(var, -1.0);
138 }
139 }
140
141 MPVariable* const num_bins_var =
142 solver.MakeIntVar(0, max_num_bins, "num_bins_var");
143 objective->SetCoefficient(
144 num_bins_var, problem.has_cost_per_bin() ? problem.cost_per_bin() : 1.0);
145 { // Source.
146 MPConstraint* const ct = solver.MakeRowConstraint(0.0, 0.0);
147 ct->SetCoefficient(num_bins_var, 1.0);
148 for (MPVariable* const var : outgoing_vars[/*source*/ 0]) {
149 ct->SetCoefficient(var, -1.0);
150 }
151 }
152
153 { // Sink.
154 MPConstraint* const ct = solver.MakeRowConstraint(0.0, 0.0);
155 const int sink_node = graph.nodes.size() - 1;
156 for (MPVariable* const var : incoming_vars[sink_node]) {
157 ct->SetCoefficient(var, 1.0);
158 }
159 ct->SetCoefficient(num_bins_var, -1.0);
160 }
161
162 if (!absl::GetFlag(FLAGS_arc_flow_dump_model).empty()) {
163 MPModelProto output_model;
164 solver.ExportModelToProto(&output_model);
165 CHECK_OK(file::SetTextProto(absl::GetFlag(FLAGS_arc_flow_dump_model),
166 output_model, file::Defaults()));
167 }
168
169 solver.EnableOutput();
170 solver.SetSolverSpecificParametersAsString(mip_params);
171 solver.SetTimeLimit(absl::Seconds(time_limit));
172 const MPSolver::ResultStatus result_status = solver.Solve();
173
174 vbp::VectorBinPackingSolution solution;
175 solution.set_solve_time_in_seconds(solver.wall_time() / 1000.0);
176 solution.set_arc_flow_time_in_seconds(arc_flow_time);
177 // Check that the problem has an optimal solution.
178 if (result_status == MPSolver::OPTIMAL) {
179 solution.set_status(vbp::OPTIMAL);
180 solution.set_objective_value(objective->Value());
181 } else if (result_status == MPSolver::FEASIBLE) {
182 solution.set_status(vbp::FEASIBLE);
183 solution.set_objective_value(objective->Value());
184 } else if (result_status == MPSolver::INFEASIBLE) {
185 solution.set_status(vbp::INFEASIBLE);
186 }
187
188 if (result_status == MPSolver::OPTIMAL ||
189 result_status == MPSolver::FEASIBLE) {
190 // Create the arc flow graph with the flow quantity in each arc.
191 struct NextCountItem {
192 int next = -1;
193 int count = 0;
194 int item = -1;
195 };
196 std::vector<std::vector<NextCountItem>> node_to_next_count_item(
197 graph.nodes.size());
198 for (int v = 0; v < graph.arcs.size(); ++v) {
199 const int count =
200 static_cast<int>(std::round(arc_to_var[v]->solution_value()));
201 if (count == 0) continue;
202 const ArcFlowGraph::Arc& arc = graph.arcs[v];
203 node_to_next_count_item[arc.source].push_back(
204 {arc.destination, count, arc.item_index});
205 }
206
207 // Unroll each possible path and rebuild bins.
208 struct NextItem {
209 int next = -1;
210 int item = -1;
211 };
212 const auto pop_next_item = [&node_to_next_count_item](int node) {
213 CHECK(!node_to_next_count_item[node].empty());
214 auto& [next, count, item] = node_to_next_count_item[node].back();
215 CHECK_NE(next, -1);
216 CHECK_GT(count, 0);
217 // Copy next and item before popping.
218 const NextItem result{next, item};
219 if (--count == 0) {
220 node_to_next_count_item[node].pop_back();
221 }
222 return result;
223 };
224
225 VLOG(1) << "Packing used " << num_bins_var->solution_value()
226 << " total bins";
227 const int start_node = 0;
228 const int end_node = graph.nodes.size() - 1;
229 while (!node_to_next_count_item[start_node].empty()) {
230 absl::btree_map<int, int> item_count;
231 int current = start_node;
232 while (current != end_node) {
233 const auto [next, item] = pop_next_item(current);
234 if (item != -1) {
235 item_count[item]++;
236 }
237 current = next;
238 }
239 vbp::VectorBinPackingOneBinInSolution* bin = solution.add_bins();
240 for (const auto& [item, count] : item_count) {
241 bin->add_item_indices(item);
242 bin->add_item_copies(count);
243 }
244 }
245 CHECK_EQ(solution.bins_size(), std::round(num_bins_var->solution_value()));
246 for (const auto& next_counts : node_to_next_count_item) {
247 CHECK(next_counts.empty());
248 }
249 }
250
251 return solution;
252}
253
254} // namespace packing
255} // namespace operations_research
ABSL_FLAG(std::string, arc_flow_dump_model, "", "File to store the solver specific optimization proto.")
double Get() const
Definition timer.h:46
void Start()
When Start() is called multiple times, only the most recent is used.
Definition timer.h:32
void SetCoefficient(const MPVariable *var, double coeff)
A class to express a linear objective.
void SetCoefficient(const MPVariable *var, double coeff)
void SetOffset(double value)
Sets the constant term in the objective.
double offset() const
Gets the constant term in the objective.
MPVariable * MakeIntVar(double lb, double ub, const std::string &name)
Creates an integer variable.
@ FEASIBLE
feasible, or stopped by limit.
@ INFEASIBLE
proven infeasible.
ResultStatus Solve()
Solves the problem using the default parameter values.
void SetTimeLimit(absl::Duration time_limit)
bool SetSolverSpecificParametersAsString(const std::string &parameters)
absl::Status SetNumThreads(int num_threads)
-— Solver-specific parameters -—
void ExportModelToProto(MPModelProto *output_model) const
Exports model to protocol buffer.
MPObjective * MutableObjective()
Returns the mutable objective object.
void EnableOutput()
Enables solver logging.
MPConstraint * MakeRowConstraint(double lb, double ub)
The class for variables of a Mathematical Programming (MP) model.
double solution_value() const
--— MPVariable --—
Block * next
GraphType graph
const Constraint * ct
IntVar * var
time_limit
Definition solve.cc:22
int arc
double solution
absl::Status SetTextProto(absl::string_view filename, const google::protobuf::Message &proto, Options options)
Definition file.cc:337
Options Defaults()
Definition file.h:109
vbp::VectorBinPackingSolution SolveVectorBinPackingWithArcFlow(const vbp::VectorBinPackingProblem &problem, MPSolver::OptimizationProblemType solver_type, const std::string &mip_params, double time_limit, int num_threads, int max_bins)
ArcFlowGraph BuildArcFlowGraph(const std::vector< int > &bin_dimensions, absl::Span< const std::vector< int > > item_dimensions_by_type, absl::Span< const int > demand_by_type)
Main method.
In SWIG mode, we don't want anything besides these top-level includes.
static int input(yyscan_t yyscanner)