OR-Tools  9.6
arc_flow_solver.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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"
23 #include "ortools/base/helpers.h"
24 #include "ortools/base/timer.h"
26 #include "ortools/packing/vector_bin_packing.pb.h"
27 
28 ABSL_FLAG(std::string, arc_flow_dump_model, "",
29  "File to store the solver specific optimization proto.");
30 
31 namespace operations_research {
32 namespace packing {
33 
34 namespace {
35 double 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 
78 vbp::VectorBinPackingSolution SolveVectorBinPackingWithArcFlow(
79  const vbp::VectorBinPackingProblem& problem,
81  const std::string& mip_params, double time_limit, int num_threads,
82  int max_bins) {
83  ArcFlowGraph graph;
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.")
void Start()
Definition: timer.h:31
double Get() const
Definition: timer.h:45
The class for constraints of a Mathematical Programming (MP) model.
A class to express a linear objective.
void SetCoefficient(const MPVariable *const var, double coeff)
Sets the coefficient of the variable in the objective.
void SetOffset(double value)
Sets the constant term in the objective.
double Value() const
Returns the objective value of the best solution found so far.
double offset() const
Gets the constant term in the objective.
This mathematical programming (MP) solver class is the main class though which users build and solve ...
MPObjective * MutableObjective()
Returns the mutable objective object.
MPConstraint * MakeRowConstraint(double lb, double ub)
Creates a linear constraint with given bounds.
ResultStatus
The status of solving the problem.
@ FEASIBLE
feasible, or stopped by limit.
@ INFEASIBLE
proven infeasible.
OptimizationProblemType
The type of problems (LP or MIP) that will be solved and the underlying solver (GLOP,...
bool SetSolverSpecificParametersAsString(const std::string &parameters)
Advanced usage: pass solver specific parameters in text format.
absl::Status SetNumThreads(int num_threads)
Sets the number of threads to use by the underlying solver.
void ExportModelToProto(MPModelProto *output_model) const
Exports model to protocol buffer.
MPVariable * MakeIntVar(double lb, double ub, const std::string &name)
Creates an integer variable.
ResultStatus Solve()
Solves the problem using the default parameter values.
void EnableOutput()
Enables solver logging.
void SetTimeLimit(absl::Duration time_limit)
The class for variables of a Mathematical Programming (MP) model.
double solution_value() const
Returns the value of the variable in the current solution.
Block * next
ModelSharedTimeLimit * time_limit
const Constraint * ct
IntVar * var
Definition: expr_array.cc:1874
int arc
absl::Status SetTextProto(const absl::string_view &filename, const google::protobuf::Message &proto, int flags)
Definition: base/file.cc:299
Options Defaults()
Definition: base/file.h:123
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, const std::vector< std::vector< int >> &item_dimensions_by_type, const std::vector< int > &demand_by_type)
Collection of objects used to extend the Constraint Solver library.
static int input(yyscan_t yyscanner)
std::vector< std::vector< int > > nodes
#define VLOG(verboselevel)
Definition: vlog.h:39