OR-Tools  9.0
facility_lp_benders.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 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 
14 // Simple linear programming example
15 
16 #include <algorithm>
17 #include <iostream>
18 #include <limits>
19 #include <utility>
20 #include <vector>
21 
22 #include "absl/flags/parse.h"
23 #include "absl/flags/usage.h"
24 #include "ortools/base/logging.h"
25 #include "absl/container/flat_hash_map.h"
26 #include "absl/flags/flag.h"
27 #include "absl/random/random.h"
28 #include "absl/random/uniform_int_distribution.h"
29 #include "absl/status/statusor.h"
30 #include "absl/strings/str_format.h"
31 #include "absl/strings/string_view.h"
32 #include "absl/time/clock.h"
33 #include "absl/time/time.h"
35 
36 ABSL_FLAG(int, num_facilities, 3000, "Number of facilities.");
37 ABSL_FLAG(int, num_locations, 50, "Number of locations.");
38 ABSL_FLAG(double, edge_probability, 0.99, "Edge probability.");
39 ABSL_FLAG(double, benders_precission, 1e-9, "Benders target precission.");
40 ABSL_FLAG(double, location_demand, 1, "Client demands.");
41 ABSL_FLAG(double, facility_cost, 100, "Facility capacity cost.");
43  double, location_fraction, 0.001,
44  "Fraction of a facility's capacity that can be used by each location.");
45 
46 namespace {
47 using ::operations_research::math_opt::GurobiParametersProto;
48 using ::operations_research::math_opt::LinearConstraint;
49 using ::operations_research::math_opt::LinearExpression;
50 using ::operations_research::math_opt::MathOpt;
51 using ::operations_research::math_opt::Objective;
52 using ::operations_research::math_opt::Result;
53 using ::operations_research::math_opt::SolveParametersProto;
54 using ::operations_research::math_opt::SolveResultProto;
56 using ::operations_research::math_opt::Variable;
57 
58 // First element is a facility and second is a location.
59 using Edge = std::pair<int, int>;
60 
61 // A simple randomly-generated facility-location network.
62 class Network {
63  public:
64  Network(int num_facilities, int num_locations, double edge_probability);
65 
66  int num_facilities() const { return num_facilities_; }
67  int num_locations() const { return num_locations_; }
68 
69  const std::vector<Edge>& edges() const { return edges_; }
70 
71  const std::vector<Edge>& edges_incident_to_facility(
72  const int facility) const {
73  return facility_edge_incidence_[facility];
74  }
75 
76  const std::vector<Edge>& edges_incident_to_location(
77  const int location) const {
78  return location_edge_incidence_[location];
79  }
80 
81  double edge_cost(const Edge& edge) const { return edge_costs_.at(edge); }
82 
83  private:
84  int num_facilities_;
85  int num_locations_;
86  // No order is assumed for the following lists of edges.
87  std::vector<Edge> edges_;
88  absl::flat_hash_map<Edge, double> edge_costs_;
89  std::vector<std::vector<Edge>> facility_edge_incidence_;
90  std::vector<std::vector<Edge>> location_edge_incidence_;
91 };
92 
93 Network::Network(const int num_facilities, const int num_locations,
94  const double edge_probability) {
95  absl::BitGen bitgen;
96 
97  num_facilities_ = num_facilities;
98  num_locations_ = num_locations;
99  facility_edge_incidence_ =
100  std::vector<std::vector<std::pair<int, int>>>(num_facilities);
101  location_edge_incidence_ = std::vector<std::vector<std::pair<int, int>>>(
102  num_locations, std::vector<std::pair<int, int>>());
103  for (int facility = 0; facility < num_facilities_; ++facility) {
104  for (int location = 0; location < num_locations_; ++location) {
105  if (absl::Bernoulli(bitgen, edge_probability)) {
106  const std::pair<int, int> edge({facility, location});
107  facility_edge_incidence_[facility].push_back(edge);
108  location_edge_incidence_[location].push_back(edge);
109  edges_.push_back(edge);
110  edge_costs_.insert({edge, absl::Uniform(bitgen, 0, 1.0)});
111  }
112  }
113  }
114  // Ensure every facility is connected to at least one location and every
115  // location is connected to at least one facility.
116  for (int facility = 0; facility < num_facilities; ++facility) {
117  auto& locations = facility_edge_incidence_[facility];
118  if (locations.empty()) {
119  const int location =
120  absl::uniform_int_distribution<int>(0, num_locations - 1)(bitgen);
121  const std::pair<int, int> edge({facility, location});
122  locations.push_back(edge);
123  location_edge_incidence_[location].push_back(edge);
124  edges_.push_back(edge);
125  edge_costs_.insert({edge, absl::Uniform(bitgen, 0, 1.0)});
126  }
127  }
128  for (int location = 0; location < num_locations; ++location) {
129  auto& facilities = location_edge_incidence_[location];
130  if (facilities.empty()) {
131  const int facility =
132  absl::uniform_int_distribution<int>(0, num_facilities - 1)(bitgen);
133  const std::pair<int, int> edge({facility, location});
134  facilities.push_back(edge);
135  facility_edge_incidence_[facility].push_back(edge);
136  edges_.push_back(edge);
137  edge_costs_.insert({edge, absl::Uniform(bitgen, 0, 1.0)});
138  }
139  }
140 }
141 
142 constexpr double kInf = std::numeric_limits<double>::infinity();
143 
144 // We consider a network design problem where each location has a demand that
145 // must be met by its neighboring facilities, and each facility can control
146 // its total capacity. In this version we also require that locations cannot
147 // use more that a specified fraction of a facilities capacity.
148 //
149 // Problem data:
150 // * F: set of facilities.
151 // * L: set of locations.
152 // * E: subset of {(f,l) : f in F, l in L} that describes the network between
153 // facilities and locations.
154 // * d: demand at location (all demands are equal for simplicity).
155 // * c: cost per unit of capacity at a facility (all facilities are have the
156 // same cost for simplicity).
157 // * h: cost per unit transported through an edge.
158 // * a: fraction of a facility's capacity that can be used by each location.
159 //
160 // Decision variables:
161 // * z_f: capacity at facility f in F.
162 // * x_(f,l): flow from facility f to location l for all (f,l) in E.
163 //
164 // Formulation:
165 // min c * sum(z_f : f in F) + sum(h_e * x_e : e in E)
166 // s.t.
167 // x_(f,l) <= a * z_f for all (f,l) in E
168 // sum(x_(f,l) : l such that (f,l) in E) <= z_f for all f in F
169 // sum(x_(f,l) : f such that (f,l) in E) >= d for all l in L
170 // x_e >= 0 for all e in E
171 // z_f >= 0 for all f in F
172 //
173 void FullProblem(const Network& network, const double location_demand,
174  const double facility_cost, const double location_fraction) {
175  const int num_facilities = network.num_facilities();
176  const int num_locations = network.num_locations();
177 
178  MathOpt model(operations_research::math_opt::SOLVER_TYPE_GUROBI,
179  "Full network design problem");
180  const Objective objective = model.objective();
181  objective.set_minimize();
182 
183  // Capacity variables
184  std::vector<Variable> z;
185  for (int j = 0; j < num_facilities; j++) {
186  const Variable z_j = model.AddContinuousVariable(0.0, kInf);
187  z.push_back(z_j);
188  objective.set_linear_coefficient(z_j, facility_cost);
189  }
190 
191  // Flow variables
192  absl::flat_hash_map<Edge, Variable> x;
193  for (const auto& edge : network.edges()) {
194  const Variable x_edge = model.AddContinuousVariable(0.0, kInf);
195  x.insert({edge, x_edge});
196  objective.set_linear_coefficient(x_edge, network.edge_cost(edge));
197  }
198 
199  // Demand constraints
200  for (int location = 0; location < num_locations; ++location) {
201  LinearExpression linear_expression;
202  for (const auto& edge : network.edges_incident_to_location(location)) {
203  linear_expression += x.at(edge);
204  }
205  model.AddLinearConstraint(linear_expression >= location_demand);
206  }
207 
208  // Supply constraints
209  for (int facility = 0; facility < num_facilities; ++facility) {
210  LinearExpression linear_expression;
211  for (const auto& edge : network.edges_incident_to_facility(facility)) {
212  linear_expression += x.at(edge);
213  }
214  model.AddLinearConstraint(linear_expression <= z[facility]);
215  }
216 
217  // Arc constraints
218  for (int facility = 0; facility < num_facilities; ++facility) {
219  for (const auto& edge : network.edges_incident_to_facility(facility)) {
220  model.AddLinearConstraint(x.at(edge) <= location_fraction * z[facility]);
221  }
222  }
223  const Result result = model.Solve(SolveParametersProto()).value();
224  for (const auto& warning : result.warnings) {
225  LOG(WARNING) << "Solver warning: " << warning << std::endl;
226  }
227  QCHECK_EQ(result.termination_reason, SolveResultProto::OPTIMAL)
228  << "Failed to find an optimal solution: " << result.termination_detail;
229  std::cout << "Full problem optimal objective: "
230  << absl::StrFormat("%.9f", result.objective_value()) << std::endl;
231 }
232 
233 void Benders(const Network network, const double location_demand,
234  const double facility_cost, const double location_fraction,
235  const double target_precission,
236  const int maximum_iterations = 30000) {
237  const int num_facilities = network.num_facilities();
238  const int num_locations = network.num_locations();
239 
240  // Setup first stage model.
241  //
242  // min c * sum(z_f : f in F) + w
243  // s.t.
244  // z_f >= 0 for all f in F
245  // sum(fcut_f^i z_f) + fcut_const^i <= 0 for i = 1,...
246  // sum(ocut_f^j z_f) + ocut_const^j <= w for j = 1,...
247  MathOpt first_stage_model(operations_research::math_opt::SOLVER_TYPE_GUROBI,
248  "First stage problem");
249  std::vector<Variable> z;
250  for (int j = 0; j < num_facilities; j++) {
251  z.push_back(first_stage_model.AddContinuousVariable(0.0, kInf));
252  }
253 
254  const Variable w = first_stage_model.AddContinuousVariable(0.0, kInf);
255 
256  first_stage_model.objective().Minimize(facility_cost * Sum(z) + w);
257 
258  SolveParametersProto first_stage_params;
259  first_stage_params.mutable_common_parameters()->set_enable_output(false);
260 
261  // Setup second stage model.
262  // min sum(h_e * x_e : e in E)
263  // s.t.
264  // x_(f,l) <= a * zz_f for all (f,l) in E
265  // sum(x_(f,l) : l such that (f,l) in E) <= zz_f for all f in F
266  // sum(x_(f,l) : f such that (f,l) in E) >= d for all l in L
267  // x_e >= 0 for all e in E
268  //
269  // where zz_f are fixed values for z_f from the first stage model.
270  MathOpt second_stage_model(operations_research::math_opt::SOLVER_TYPE_GUROBI,
271  "Second stage model");
272  const Objective second_stage_objective = second_stage_model.objective();
273  second_stage_objective.set_minimize();
274  absl::flat_hash_map<Edge, Variable> x;
275  for (const auto& edge : network.edges()) {
276  const Variable x_edge = second_stage_model.AddContinuousVariable(0.0, kInf);
277  x.insert({edge, x_edge});
278  second_stage_objective.set_linear_coefficient(x_edge,
279  network.edge_cost(edge));
280  }
281  std::vector<LinearConstraint> demand_constraints;
282 
283  for (int location = 0; location < num_locations; ++location) {
284  LinearExpression linear_expression;
285  for (const auto& edge : network.edges_incident_to_location(location)) {
286  linear_expression += x.at(edge);
287  }
288  demand_constraints.push_back(second_stage_model.AddLinearConstraint(
289  linear_expression >= location_demand));
290  }
291  std::vector<LinearConstraint> supply_constraints;
292  for (int facility = 0; facility < num_facilities; ++facility) {
293  LinearExpression linear_expression;
294  for (const auto& edge : network.edges_incident_to_facility(facility)) {
295  linear_expression += x.at(edge);
296  }
297  supply_constraints.push_back(
298  second_stage_model.AddLinearConstraint(linear_expression <= kInf));
299  }
300 
301  SolveParametersProto second_stage_params;
302  second_stage_params.mutable_common_parameters()->set_enable_output(false);
303  GurobiParametersProto::Parameter* param1 =
304  second_stage_params.mutable_gurobi_parameters()->add_parameters();
305  param1->set_name("InfUnbdInfo");
306  param1->set_value("1");
307 
308  // Start Benders
309  int iteration = 0;
310  double best_upper_bound = kInf;
311  while (true) {
312  LOG(INFO) << "Iteration: " << iteration;
313  // Solve and process first stage.
314  const Result first_stage_result =
315  first_stage_model.Solve(first_stage_params).value();
316  for (const auto& warning : first_stage_result.warnings) {
317  LOG(WARNING) << "Solver warning: " << warning << std::endl;
318  }
319  QCHECK_EQ(first_stage_result.termination_reason, SolveResultProto::OPTIMAL);
320  const double lower_bound = first_stage_result.objective_value();
321  LOG(INFO) << "LB = " << lower_bound;
322 
323  // Setup second stage.
324  for (int facility = 0; facility < num_facilities; ++facility) {
325  const double capacity_value =
326  first_stage_result.variable_values().at(z[facility]);
327  for (const auto& edge : network.edges_incident_to_facility(facility)) {
328  x.at(edge).set_upper_bound(location_fraction * capacity_value);
329  }
330  supply_constraints[facility].set_upper_bound(capacity_value);
331  }
332 
333  // Solve and process second stage.
334  const Result second_stage_result =
335  second_stage_model.Solve(second_stage_params).value();
336  for (const auto& warning : second_stage_result.warnings) {
337  LOG(WARNING) << "Solver warning: " << warning << std::endl;
338  }
339  if (second_stage_result.termination_reason ==
341  // If the second stage problem is infeasible we will get a dual ray
342  // (r, y) such that
343  //
344  // sum(r_(f,l)*a*zz_f : (f,l) in E, r_(f,l) < 0)
345  // + sum(y_f*zz_f : f in F, y_f < 0)
346  // + sum(y_l*d : l in L, y_l > 0) > 0.
347  //
348  // Then we get the feasibility cut (go/mathopt-advanced-dual-use#benders)
349  //
350  // sum(fcut_f*z_f) + fcut_const <= 0,
351  //
352  // where
353  //
354  // fcut_f = sum(r_(f,l)*a : (f,l) in E, r_(f,l) < 0)
355  // + min{y_f, 0}
356  // fcut_const = sum*(y_l*d : l in L, y_l > 0)
357  LOG(INFO) << "Adding feasibility cut...";
358  LinearExpression feasibility_cut_expression;
359  for (int facility = 0; facility < num_facilities; ++facility) {
360  double coefficient = 0.0;
361  for (const auto& edge : network.edges_incident_to_facility(facility)) {
362  const double reduced_cost =
363  second_stage_result.ray_reduced_costs().at(x.at(edge));
364  if (reduced_cost < 0) {
365  coefficient += location_fraction * reduced_cost;
366  }
367  }
368  double dual_value = second_stage_result.ray_dual_values().at(
369  supply_constraints[facility]);
370  coefficient += std::min(dual_value, 0.0);
371  feasibility_cut_expression += coefficient * z[facility];
372  }
373  double constant = 0.0;
374  for (const auto& constraint : demand_constraints) {
375  double dual_value =
376  second_stage_result.ray_dual_values().at(constraint);
377  if (dual_value > 0) {
378  constant += dual_value;
379  }
380  }
381  first_stage_model.AddLinearConstraint(
382  feasibility_cut_expression + constant <= 0.0);
383  } else {
384  // If the second stage problem is optimal we will get a dual solution
385  // (r, y) such that the optimal objective value is equal to
386  //
387  // sum(r_(f,l)*a*zz_f : (f,l) in E, r_(f,l) < 0)
388  // + sum(y_f*zz_f : f in F, y_f < 0)
389  // + sum*(y_l*d : l in L, y_l > 0) > 0.
390  //
391  // Then we get the optimality cut (go/mathopt-advanced-dual-use#benders)
392  //
393  // sum(ocut_f*z_f) + ocut_const <= w,
394  //
395  // where
396  //
397  // ocut_f = sum(r_(f,l)*a : (f,l) in E, r_(f,l) < 0)
398  // + min{y_f, 0}
399  // ocut_const = sum*(y_l*d : l in L, y_l > 0)
400  QCHECK_EQ(second_stage_result.termination_reason,
402  LOG(INFO) << "Adding optimality cut...";
403  LinearExpression optimality_cut_expression;
404  double upper_bound = 0.0;
405  for (int facility = 0; facility < num_facilities; ++facility) {
406  double coefficient = 0.0;
407  for (const auto& edge : network.edges_incident_to_facility(facility)) {
408  upper_bound += network.edge_cost(edge) *
409  second_stage_result.variable_values().at(x.at(edge));
410  const double reduced_cost =
411  second_stage_result.reduced_costs().at(x.at(edge));
412  if (reduced_cost < 0) {
413  coefficient += location_fraction * reduced_cost;
414  }
415  }
416  double dual_value =
417  second_stage_result.dual_values().at(supply_constraints[facility]);
418  coefficient += std::min(dual_value, 0.0);
419  optimality_cut_expression += coefficient * z[facility];
420  }
421  double constant = 0.0;
422  for (const auto& constraint : demand_constraints) {
423  double dual_value = second_stage_result.dual_values().at(constraint);
424  if (dual_value > 0) {
425  constant += dual_value;
426  }
427  }
428  // Add first stage contribution to upper bound = facility_cost * Sum(z)
429  upper_bound += first_stage_result.objective_value() -
430  first_stage_result.variable_values().at(w);
431  best_upper_bound = std::min(best_upper_bound, upper_bound);
432 
433  first_stage_model.AddLinearConstraint(
434  optimality_cut_expression + constant <= w);
435  }
436  LOG(INFO) << "UB = " << best_upper_bound;
437  ++iteration;
438  if (best_upper_bound - lower_bound < target_precission) {
439  std::cout << "Total iterations = " << iteration << std::endl;
440  std::cout << "Final LB = " << absl::StrFormat("%.9f", lower_bound)
441  << std::endl;
442  std::cout << "Final UB = " << absl::StrFormat("%.9f", best_upper_bound)
443  << std::endl;
444  break;
445  }
446  if (iteration > maximum_iterations) {
447  break;
448  }
449  }
450 }
451 
452 } // namespace
453 
454 int main(int argc, char** argv) {
455  google::InitGoogleLogging(argv[0]);
456 absl::ParseCommandLine(argc, argv);
457  const Network network(absl::GetFlag(FLAGS_num_facilities),
458  absl::GetFlag(FLAGS_num_locations),
459  absl::GetFlag(FLAGS_edge_probability));
460  absl::Time start = absl::Now();
461  FullProblem(network, absl::GetFlag(FLAGS_location_demand),
462  absl::GetFlag(FLAGS_facility_cost),
463  absl::GetFlag(FLAGS_location_fraction));
464  std::cout << "Full solve time : " << absl::Now() - start << std::endl;
465  start = absl::Now();
466  Benders(network, absl::GetFlag(FLAGS_location_demand),
467  absl::GetFlag(FLAGS_facility_cost),
468  absl::GetFlag(FLAGS_location_fraction),
469  absl::GetFlag(FLAGS_benders_precission));
470  std::cout << "Benders solve time : " << absl::Now() - start << std::endl;
471  return 0;
472 }
int64_t min
Definition: alldiff_cst.cc:139
#define LOG(severity)
Definition: base/logging.h:423
#define QCHECK_EQ
Definition: base/logging.h:40
int main(int argc, char **argv)
ABSL_FLAG(int, num_facilities, 3000, "Number of facilities.")
double upper_bound
double lower_bound
GRBmodel * model
const int WARNING
Definition: log_severity.h:31
const int INFO
Definition: log_severity.h:31
void InitGoogleLogging(const char *argv0)
LinearExpression Sum(const Iterable &items)
int64_t coefficient