OR-Tools  9.2
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/container/flat_hash_map.h"
23#include "absl/flags/flag.h"
24#include "absl/flags/parse.h"
25#include "absl/flags/usage.h"
26#include "absl/random/random.h"
27#include "absl/random/uniform_int_distribution.h"
28#include "absl/status/statusor.h"
29#include "absl/strings/str_format.h"
30#include "absl/strings/string_view.h"
31#include "absl/time/clock.h"
32#include "absl/time/time.h"
35
36ABSL_FLAG(int, num_facilities, 3000, "Number of facilities.");
37ABSL_FLAG(int, num_locations, 50, "Number of locations.");
38ABSL_FLAG(double, edge_probability, 0.99, "Edge probability.");
39ABSL_FLAG(double, benders_precission, 1e-9, "Benders target precission.");
40ABSL_FLAG(double, location_demand, 1, "Client demands.");
41ABSL_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
46namespace {
47using ::operations_research::math_opt::IncrementalSolver;
48using ::operations_research::math_opt::LinearConstraint;
49using ::operations_research::math_opt::LinearExpression;
50using ::operations_research::math_opt::Model;
51using ::operations_research::math_opt::SolveArguments;
52using ::operations_research::math_opt::SolveResult;
56using ::operations_research::math_opt::Variable;
57
58// First element is a facility and second is a location.
59using Edge = std::pair<int, int>;
60
61// A simple randomly-generated facility-location network.
62class 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
93Network::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
142constexpr 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//
173void 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 Model model("Full network design problem");
179 model.set_minimize();
180
181 // Capacity variables
182 std::vector<Variable> z;
183 for (int j = 0; j < num_facilities; j++) {
184 const Variable z_j = model.AddContinuousVariable(0.0, kInf);
185 z.push_back(z_j);
186 model.set_objective_coefficient(z_j, facility_cost);
187 }
188
189 // Flow variables
190 absl::flat_hash_map<Edge, Variable> x;
191 for (const auto& edge : network.edges()) {
192 const Variable x_edge = model.AddContinuousVariable(0.0, kInf);
193 x.insert({edge, x_edge});
194 model.set_objective_coefficient(x_edge, network.edge_cost(edge));
195 }
196
197 // Demand constraints
198 for (int location = 0; location < num_locations; ++location) {
199 LinearExpression linear_expression;
200 for (const auto& edge : network.edges_incident_to_location(location)) {
201 linear_expression += x.at(edge);
202 }
203 model.AddLinearConstraint(linear_expression >= location_demand);
204 }
205
206 // Supply constraints
207 for (int facility = 0; facility < num_facilities; ++facility) {
208 LinearExpression linear_expression;
209 for (const auto& edge : network.edges_incident_to_facility(facility)) {
210 linear_expression += x.at(edge);
211 }
212 model.AddLinearConstraint(linear_expression <= z[facility]);
213 }
214
215 // Arc constraints
216 for (int facility = 0; facility < num_facilities; ++facility) {
217 for (const auto& edge : network.edges_incident_to_facility(facility)) {
218 model.AddLinearConstraint(x.at(edge) <= location_fraction * z[facility]);
219 }
220 }
221 const SolveResult result = Solve(model, SolverType::kGurobi).value();
222 for (const auto& warning : result.warnings) {
223 LOG(WARNING) << "Solver warning: " << warning << std::endl;
224 }
225 QCHECK_EQ(result.termination.reason, TerminationReason::kOptimal)
226 << "Failed to find an optimal solution: " << result.termination;
227 std::cout << "Full problem optimal objective: "
228 << absl::StrFormat("%.9f", result.objective_value()) << std::endl;
229}
230
231void Benders(const Network network, const double location_demand,
232 const double facility_cost, const double location_fraction,
233 const double target_precission,
234 const int maximum_iterations = 30000) {
235 const int num_facilities = network.num_facilities();
236 const int num_locations = network.num_locations();
237
238 // Setup first stage model.
239 //
240 // min c * sum(z_f : f in F) + w
241 // s.t.
242 // z_f >= 0 for all f in F
243 // sum(fcut_f^i z_f) + fcut_const^i <= 0 for i = 1,...
244 // sum(ocut_f^j z_f) + ocut_const^j <= w for j = 1,...
245 Model first_stage_model("First stage problem");
246 std::vector<Variable> z;
247 for (int j = 0; j < num_facilities; j++) {
248 z.push_back(first_stage_model.AddContinuousVariable(0.0, kInf));
249 }
250
251 const Variable w = first_stage_model.AddContinuousVariable(0.0, kInf);
252
253 first_stage_model.Minimize(facility_cost * Sum(z) + w);
254
255 // Setup second stage model.
256 // min sum(h_e * x_e : e in E)
257 // s.t.
258 // x_(f,l) <= a * zz_f for all (f,l) in E
259 // sum(x_(f,l) : l such that (f,l) in E) <= zz_f for all f in F
260 // sum(x_(f,l) : f such that (f,l) in E) >= d for all l in L
261 // x_e >= 0 for all e in E
262 //
263 // where zz_f are fixed values for z_f from the first stage model.
264 Model second_stage_model("Second stage model");
265 second_stage_model.set_minimize();
266 absl::flat_hash_map<Edge, Variable> x;
267 for (const auto& edge : network.edges()) {
268 const Variable x_edge = second_stage_model.AddContinuousVariable(0.0, kInf);
269 x.insert({edge, x_edge});
270 second_stage_model.set_objective_coefficient(x_edge,
271 network.edge_cost(edge));
272 }
273 std::vector<LinearConstraint> demand_constraints;
274
275 for (int location = 0; location < num_locations; ++location) {
276 LinearExpression linear_expression;
277 for (const auto& edge : network.edges_incident_to_location(location)) {
278 linear_expression += x.at(edge);
279 }
280 demand_constraints.push_back(second_stage_model.AddLinearConstraint(
281 linear_expression >= location_demand));
282 }
283 std::vector<LinearConstraint> supply_constraints;
284 for (int facility = 0; facility < num_facilities; ++facility) {
285 LinearExpression linear_expression;
286 for (const auto& edge : network.edges_incident_to_facility(facility)) {
287 linear_expression += x.at(edge);
288 }
289 supply_constraints.push_back(
290 second_stage_model.AddLinearConstraint(linear_expression <= kInf));
291 }
292
293 SolveArguments second_stage_args;
294 second_stage_args.parameters.gurobi.param_values["InfUnbdInfo"] = "1";
295
296 // Start Benders
297 int iteration = 0;
298 double best_upper_bound = kInf;
299 const std::unique_ptr<IncrementalSolver> first_stage_solver =
300 IncrementalSolver::New(first_stage_model, SolverType::kGurobi).value();
301 const std::unique_ptr<IncrementalSolver> second_stage_solver =
302 IncrementalSolver::New(second_stage_model, SolverType::kGurobi).value();
303 while (true) {
304 LOG(INFO) << "Iteration: " << iteration;
305 // Solve and process first stage.
306 const SolveResult first_stage_result = first_stage_solver->Solve().value();
307 for (const auto& warning : first_stage_result.warnings) {
308 LOG(WARNING) << "Solver warning: " << warning << std::endl;
309 }
310 QCHECK_EQ(first_stage_result.termination.reason,
311 TerminationReason::kOptimal)
312 << first_stage_result.termination;
313 const double lower_bound = first_stage_result.objective_value();
314 LOG(INFO) << "LB = " << lower_bound;
315
316 // Setup second stage.
317 for (int facility = 0; facility < num_facilities; ++facility) {
318 const double capacity_value =
319 first_stage_result.variable_values().at(z[facility]);
320 for (const auto& edge : network.edges_incident_to_facility(facility)) {
321 second_stage_model.set_upper_bound(x.at(edge),
322 location_fraction * capacity_value);
323 }
324 second_stage_model.set_upper_bound(supply_constraints[facility],
325 capacity_value);
326 }
327
328 // Solve and process second stage.
329 const SolveResult second_stage_result =
330 second_stage_solver->Solve(second_stage_args).value();
331 for (const auto& warning : second_stage_result.warnings) {
332 LOG(WARNING) << "Solver warning: " << warning << std::endl;
333 }
334 if (second_stage_result.termination.reason ==
335 TerminationReason::kInfeasible) {
336 // If the second stage problem is infeasible we will get a dual ray
337 // (r, y) such that
338 //
339 // sum(r_(f,l)*a*zz_f : (f,l) in E, r_(f,l) < 0)
340 // + sum(y_f*zz_f : f in F, y_f < 0)
341 // + sum(y_l*d : l in L, y_l > 0) > 0.
342 //
343 // Then we get the feasibility cut (go/mathopt-advanced-dual-use#benders)
344 //
345 // sum(fcut_f*z_f) + fcut_const <= 0,
346 //
347 // where
348 //
349 // fcut_f = sum(r_(f,l)*a : (f,l) in E, r_(f,l) < 0)
350 // + min{y_f, 0}
351 // fcut_const = sum*(y_l*d : l in L, y_l > 0)
352 LOG(INFO) << "Adding feasibility cut...";
353 LinearExpression feasibility_cut_expression;
354 for (int facility = 0; facility < num_facilities; ++facility) {
355 double coefficient = 0.0;
356 for (const auto& edge : network.edges_incident_to_facility(facility)) {
357 const double reduced_cost =
358 second_stage_result.ray_reduced_costs().at(x.at(edge));
359 if (reduced_cost < 0) {
360 coefficient += location_fraction * reduced_cost;
361 }
362 }
363 double dual_value = second_stage_result.ray_dual_values().at(
364 supply_constraints[facility]);
365 coefficient += std::min(dual_value, 0.0);
366 feasibility_cut_expression += coefficient * z[facility];
367 }
368 double constant = 0.0;
369 for (const auto& constraint : demand_constraints) {
370 double dual_value =
371 second_stage_result.ray_dual_values().at(constraint);
372 if (dual_value > 0) {
373 constant += dual_value;
374 }
375 }
376 first_stage_model.AddLinearConstraint(
377 feasibility_cut_expression + constant <= 0.0);
378 } else {
379 // If the second stage problem is optimal we will get a dual solution
380 // (r, y) such that the optimal objective value is equal to
381 //
382 // sum(r_(f,l)*a*zz_f : (f,l) in E, r_(f,l) < 0)
383 // + sum(y_f*zz_f : f in F, y_f < 0)
384 // + sum*(y_l*d : l in L, y_l > 0) > 0.
385 //
386 // Then we get the optimality cut (go/mathopt-advanced-dual-use#benders)
387 //
388 // sum(ocut_f*z_f) + ocut_const <= w,
389 //
390 // where
391 //
392 // ocut_f = sum(r_(f,l)*a : (f,l) in E, r_(f,l) < 0)
393 // + min{y_f, 0}
394 // ocut_const = sum*(y_l*d : l in L, y_l > 0)
395 QCHECK_EQ(second_stage_result.termination.reason,
396 TerminationReason::kOptimal)
397 << second_stage_result.termination;
398 LOG(INFO) << "Adding optimality cut...";
399 LinearExpression optimality_cut_expression;
400 double upper_bound = 0.0;
401 for (int facility = 0; facility < num_facilities; ++facility) {
402 double coefficient = 0.0;
403 for (const auto& edge : network.edges_incident_to_facility(facility)) {
404 upper_bound += network.edge_cost(edge) *
405 second_stage_result.variable_values().at(x.at(edge));
406 const double reduced_cost =
407 second_stage_result.reduced_costs().at(x.at(edge));
408 if (reduced_cost < 0) {
409 coefficient += location_fraction * reduced_cost;
410 }
411 }
412 double dual_value =
413 second_stage_result.dual_values().at(supply_constraints[facility]);
414 coefficient += std::min(dual_value, 0.0);
415 optimality_cut_expression += coefficient * z[facility];
416 }
417 double constant = 0.0;
418 for (const auto& constraint : demand_constraints) {
419 double dual_value = second_stage_result.dual_values().at(constraint);
420 if (dual_value > 0) {
421 constant += dual_value;
422 }
423 }
424 // Add first stage contribution to upper bound = facility_cost * Sum(z)
425 upper_bound += first_stage_result.objective_value() -
426 first_stage_result.variable_values().at(w);
427 best_upper_bound = std::min(best_upper_bound, upper_bound);
428
429 first_stage_model.AddLinearConstraint(
430 optimality_cut_expression + constant <= w);
431 }
432 LOG(INFO) << "UB = " << best_upper_bound;
433 ++iteration;
434 if (best_upper_bound - lower_bound < target_precission) {
435 std::cout << "Total iterations = " << iteration << std::endl;
436 std::cout << "Final LB = " << absl::StrFormat("%.9f", lower_bound)
437 << std::endl;
438 std::cout << "Final UB = " << absl::StrFormat("%.9f", best_upper_bound)
439 << std::endl;
440 break;
441 }
442 if (iteration > maximum_iterations) {
443 break;
444 }
445 }
446}
447
448} // namespace
449
450int main(int argc, char** argv) {
452 absl::ParseCommandLine(argc, argv);
453 const Network network(absl::GetFlag(FLAGS_num_facilities),
454 absl::GetFlag(FLAGS_num_locations),
455 absl::GetFlag(FLAGS_edge_probability));
456 absl::Time start = absl::Now();
457 FullProblem(network, absl::GetFlag(FLAGS_location_demand),
458 absl::GetFlag(FLAGS_facility_cost),
459 absl::GetFlag(FLAGS_location_fraction));
460 std::cout << "Full solve time : " << absl::Now() - start << std::endl;
461 start = absl::Now();
462 Benders(network, absl::GetFlag(FLAGS_location_demand),
463 absl::GetFlag(FLAGS_facility_cost),
464 absl::GetFlag(FLAGS_location_fraction),
465 absl::GetFlag(FLAGS_benders_precission));
466 std::cout << "Benders solve time : " << absl::Now() - start << std::endl;
467 return 0;
468}
int64_t min
Definition: alldiff_cst.cc:139
#define LOG(severity)
Definition: base/logging.h:420
#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)
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
Definition: solve.cc:155
int64_t coefficient
int64_t start
const double constant