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"
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.");
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;
59using Edge = std::pair<int, int>;
64 Network(
int num_facilities,
int num_locations,
double edge_probability);
66 int num_facilities()
const {
return num_facilities_; }
67 int num_locations()
const {
return num_locations_; }
69 const std::vector<Edge>& edges()
const {
return edges_; }
71 const std::vector<Edge>& edges_incident_to_facility(
72 const int facility)
const {
73 return facility_edge_incidence_[facility];
76 const std::vector<Edge>& edges_incident_to_location(
77 const int location)
const {
78 return location_edge_incidence_[location];
81 double edge_cost(
const Edge& edge)
const {
return edge_costs_.at(edge); }
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_;
93Network::Network(
const int num_facilities,
const int num_locations,
94 const double edge_probability) {
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)});
116 for (
int facility = 0; facility < num_facilities; ++facility) {
117 auto& locations = facility_edge_incidence_[facility];
118 if (locations.empty()) {
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)});
128 for (
int location = 0; location < num_locations; ++location) {
129 auto& facilities = location_edge_incidence_[location];
130 if (facilities.empty()) {
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)});
142constexpr double kInf = std::numeric_limits<double>::infinity();
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();
178 Model
model(
"Full network design problem");
179 model.set_minimize();
182 std::vector<Variable> z;
183 for (
int j = 0; j < num_facilities; j++) {
184 const Variable z_j =
model.AddContinuousVariable(0.0,
kInf);
186 model.set_objective_coefficient(z_j, facility_cost);
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));
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);
203 model.AddLinearConstraint(linear_expression >= location_demand);
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);
212 model.AddLinearConstraint(linear_expression <= z[facility]);
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]);
221 const SolveResult result =
Solve(
model, SolverType::kGurobi).value();
222 for (
const auto& warning : result.warnings) {
223 LOG(
WARNING) <<
"Solver warning: " << warning << std::endl;
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;
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();
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));
251 const Variable w = first_stage_model.AddContinuousVariable(0.0,
kInf);
253 first_stage_model.Minimize(facility_cost *
Sum(z) + w);
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));
273 std::vector<LinearConstraint> demand_constraints;
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);
280 demand_constraints.push_back(second_stage_model.AddLinearConstraint(
281 linear_expression >= location_demand));
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);
289 supply_constraints.push_back(
290 second_stage_model.AddLinearConstraint(linear_expression <=
kInf));
293 SolveArguments second_stage_args;
294 second_stage_args.parameters.gurobi.param_values[
"InfUnbdInfo"] =
"1";
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();
304 LOG(
INFO) <<
"Iteration: " << iteration;
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;
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();
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);
324 second_stage_model.set_upper_bound(supply_constraints[facility],
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;
334 if (second_stage_result.termination.reason ==
335 TerminationReason::kInfeasible) {
352 LOG(
INFO) <<
"Adding feasibility cut...";
353 LinearExpression feasibility_cut_expression;
354 for (
int facility = 0; facility < num_facilities; ++facility) {
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) {
363 double dual_value = second_stage_result.ray_dual_values().at(
364 supply_constraints[facility]);
366 feasibility_cut_expression +=
coefficient * z[facility];
369 for (
const auto& constraint : demand_constraints) {
371 second_stage_result.ray_dual_values().at(constraint);
372 if (dual_value > 0) {
376 first_stage_model.AddLinearConstraint(
377 feasibility_cut_expression +
constant <= 0.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;
401 for (
int facility = 0; facility < num_facilities; ++facility) {
403 for (
const auto& edge : network.edges_incident_to_facility(facility)) {
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) {
413 second_stage_result.dual_values().at(supply_constraints[facility]);
415 optimality_cut_expression +=
coefficient * z[facility];
418 for (
const auto& constraint : demand_constraints) {
419 double dual_value = second_stage_result.dual_values().at(constraint);
420 if (dual_value > 0) {
425 upper_bound += first_stage_result.objective_value() -
426 first_stage_result.variable_values().at(w);
429 first_stage_model.AddLinearConstraint(
430 optimality_cut_expression +
constant <= w);
432 LOG(
INFO) <<
"UB = " << best_upper_bound;
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)
438 std::cout <<
"Final UB = " << absl::StrFormat(
"%.9f", best_upper_bound)
442 if (iteration > maximum_iterations) {
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;
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;
int main(int argc, char **argv)
ABSL_FLAG(int, num_facilities, 3000, "Number of facilities.")
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)