55#include "absl/container/flat_hash_map.h"
56#include "absl/flags/flag.h"
57#include "absl/random/random.h"
58#include "absl/random/seed_sequences.h"
59#include "absl/random/uniform_int_distribution.h"
60#include "absl/status/status.h"
61#include "absl/status/statusor.h"
62#include "absl/strings/str_format.h"
63#include "absl/strings/string_view.h"
64#include "absl/time/clock.h"
65#include "absl/time/time.h"
72ABSL_FLAG(
int, num_facilities, 3000,
"Number of facilities.");
73ABSL_FLAG(
int, num_locations, 50,
"Number of locations.");
74ABSL_FLAG(
double, edge_probability, 0.99,
"Edge probability.");
75ABSL_FLAG(
double, benders_precission, 1e-9,
"Benders target precission.");
76ABSL_FLAG(
double, location_demand, 1,
"Client demands.");
77ABSL_FLAG(
double, facility_cost, 100,
"Facility capacity cost.");
79 double, location_fraction, 0.001,
80 "Fraction of a facility's capacity that can be used by each location.");
83 "the LP solver to use, possible values: glop, gurobi, glpk, pdlp");
88constexpr double kInf = std::numeric_limits<double>::infinity();
96using Edge = std::pair<int, int>;
101 Network(
int num_facilities,
int num_locations,
double edge_probability);
103 int num_facilities()
const {
return num_facilities_; }
104 int num_locations()
const {
return num_locations_; }
106 const std::vector<Edge>& edges()
const {
return edges_; }
108 const std::vector<Edge>& edges_incident_to_facility(
109 const int facility)
const {
110 return facility_edge_incidence_[facility];
113 const std::vector<Edge>& edges_incident_to_location(
114 const int location)
const {
115 return location_edge_incidence_[location];
118 double edge_cost(
const Edge& edge)
const {
return edge_costs_.at(edge); }
124 std::vector<Edge> edges_;
125 absl::flat_hash_map<Edge, double> edge_costs_;
126 std::vector<std::vector<Edge>> facility_edge_incidence_;
127 std::vector<std::vector<Edge>> location_edge_incidence_;
130Network::Network(
const int num_facilities,
const int num_locations,
131 const double edge_probability) {
132 absl::SeedSeq seq({1, 2, 3});
133 absl::BitGen bitgen(seq);
135 num_facilities_ = num_facilities;
136 num_locations_ = num_locations;
137 facility_edge_incidence_ =
138 std::vector<std::vector<std::pair<int, int>>>(num_facilities);
139 location_edge_incidence_ = std::vector<std::vector<std::pair<int, int>>>(
140 num_locations, std::vector<std::pair<int, int>>());
141 for (
int facility = 0; facility < num_facilities_; ++facility) {
142 for (
int location = 0; location < num_locations_; ++location) {
143 if (absl::Bernoulli(bitgen, edge_probability)) {
144 const std::pair<int, int> edge({facility, location});
145 facility_edge_incidence_[facility].push_back(edge);
146 location_edge_incidence_[location].push_back(edge);
147 edges_.push_back(edge);
148 edge_costs_.insert({edge, absl::Uniform(bitgen, 0, 1.0)});
154 for (
int facility = 0; facility < num_facilities; ++facility) {
155 auto& locations = facility_edge_incidence_[facility];
156 if (locations.empty()) {
158 absl::uniform_int_distribution<int>(0, num_locations - 1)(bitgen);
159 const std::pair<int, int> edge({facility, location});
160 locations.push_back(edge);
161 location_edge_incidence_[location].push_back(edge);
162 edges_.push_back(edge);
163 edge_costs_.insert({edge, absl::Uniform(bitgen, 0, 1.0)});
166 for (
int location = 0; location < num_locations; ++location) {
167 auto& facilities = location_edge_incidence_[location];
168 if (facilities.empty()) {
170 absl::uniform_int_distribution<int>(0, num_facilities - 1)(bitgen);
171 const std::pair<int, int> edge({facility, location});
172 facilities.push_back(edge);
173 facility_edge_incidence_[facility].push_back(edge);
174 edges_.push_back(edge);
175 edge_costs_.insert({edge, absl::Uniform(bitgen, 0, 1.0)});
180struct FacilityLocationInstance {
182 double location_demand;
183 double facility_cost;
184 double location_fraction;
192absl::Status FullProblem(
const FacilityLocationInstance& instance,
194 const int num_facilities = instance.network.num_facilities();
195 const int num_locations = instance.network.num_locations();
200 std::vector<math_opt::Variable> z;
201 for (
int j = 0; j < num_facilities; j++) {
202 z.push_back(
model.AddContinuousVariable(0.0,
kInf));
206 absl::flat_hash_map<Edge, math_opt::Variable> x;
207 for (
const auto& edge : instance.network.edges()) {
209 x.insert({edge, x_edge});
214 for (
const auto& [edge, x_edge] : x) {
215 objective_for_edges += instance.network.edge_cost(edge) * x_edge;
217 model.Minimize(objective_for_edges +
221 for (
int location = 0; location < num_locations; ++location) {
223 for (
const auto& edge :
224 instance.network.edges_incident_to_location(location)) {
225 incomming_supply += x.at(edge);
227 model.AddLinearConstraint(incomming_supply >= instance.location_demand);
231 for (
int facility = 0; facility < num_facilities; ++facility) {
233 for (
const auto& edge :
234 instance.network.edges_incident_to_facility(facility)) {
235 outgoing_supply += x.at(edge);
237 model.AddLinearConstraint(outgoing_supply <= z[facility]);
241 for (
int facility = 0; facility < num_facilities; ++facility) {
242 for (
const auto& edge :
243 instance.network.edges_incident_to_facility(facility)) {
244 model.AddLinearConstraint(x.at(edge) <=
245 instance.location_fraction * z[facility]);
252 <<
"failed to find an optimal solution: " << result.
termination;
255 std::cout <<
"Full problem optimal objective: "
257 return absl::OkStatus();
271struct FirstStageProblem {
273 std::vector<math_opt::Variable> z;
276 FirstStageProblem(
const Network& network,
const double facility_cost);
279FirstStageProblem::FirstStageProblem(
const Network& network,
280 const double facility_cost)
281 :
model(
"First stage problem"), w(
model.AddContinuousVariable(0.0,
kInf)) {
282 const int num_facilities = network.num_facilities();
285 for (
int j = 0; j < num_facilities; j++) {
286 z.push_back(
model.AddContinuousVariable(0.0,
kInf));
290 model.Minimize(w + facility_cost *
Sum(z));
300 std::vector<double> z_coefficients;
302 double w_coefficient;
316class SecondStageSolver {
318 static absl::StatusOr<std::unique_ptr<SecondStageSolver>> New(
321 absl::StatusOr<std::pair<double, Cut>>
Solve(
322 const std::vector<double>& z_values,
double w_value,
323 double fist_stage_objective);
326 SecondStageSolver(FacilityLocationInstance instance,
329 absl::StatusOr<Cut> OptimalityCut(
331 absl::StatusOr<Cut> FeasibilityCut(
335 const Network network_;
336 const double location_fraction_;
339 absl::flat_hash_map<Edge, math_opt::Variable> x_;
340 std::vector<math_opt::LinearConstraint> supply_constraints_;
341 std::vector<math_opt::LinearConstraint> demand_constraints_;
342 std::unique_ptr<math_opt::IncrementalSolver> solver_;
345absl::StatusOr<math_opt::SolveParameters> EnsureDualRaySolveParameters(
348 switch (solver_type) {
349 case math_opt::SolverType::kGurobi:
350 parameters.gurobi.param_values[
"InfUnbdInfo"] =
"1";
352 case math_opt::SolverType::kGlop:
353 parameters.presolve = math_opt::Emphasis::kOff;
354 parameters.scaling = math_opt::Emphasis::kOff;
355 parameters.lp_algorithm = math_opt::LPAlgorithm::kDualSimplex;
357 case math_opt::SolverType::kGlpk:
358 parameters.presolve = math_opt::Emphasis::kOff;
359 parameters.lp_algorithm = math_opt::LPAlgorithm::kDualSimplex;
363 <<
"unsupported solver: " << solver_type;
368absl::StatusOr<std::unique_ptr<SecondStageSolver>> SecondStageSolver::New(
372 EnsureDualRaySolveParameters(solver_type));
374 std::unique_ptr<SecondStageSolver> second_stage_solver =
375 absl::WrapUnique<SecondStageSolver>(
376 new SecondStageSolver(std::move(instance),
parameters));
379 &second_stage_solver->second_stage_model_, solver_type));
380 second_stage_solver->solver_ = std::move(solver);
381 return std::move(second_stage_solver);
384SecondStageSolver::SecondStageSolver(
385 FacilityLocationInstance instance,
387 : second_stage_model_(
"Second stage model"),
388 network_(
std::move(instance.network)),
389 location_fraction_(instance.location_fraction),
390 second_stage_params_(second_stage_params) {
391 const int num_facilities = network_.num_facilities();
392 const int num_locations = network_.num_locations();
395 for (
const auto& edge : network_.edges()) {
397 second_stage_model_.AddContinuousVariable(0.0,
kInf);
398 x_.insert({edge, x_edge});
403 for (
const auto& [edge, x_edge] : x_) {
404 objective_for_edges += network_.edge_cost(edge) * x_edge;
406 second_stage_model_.Minimize(objective_for_edges);
409 for (
int location = 0; location < num_locations; ++location) {
411 for (
const auto& edge : network_.edges_incident_to_location(location)) {
412 incomming_supply += x_.at(edge);
414 demand_constraints_.push_back(second_stage_model_.AddLinearConstraint(
415 incomming_supply >= instance.location_demand));
419 for (
int facility = 0; facility < num_facilities; ++facility) {
421 for (
const auto& edge : network_.edges_incident_to_facility(facility)) {
422 outgoing_supply += x_.at(edge);
426 supply_constraints_.push_back(
427 second_stage_model_.AddLinearConstraint(outgoing_supply <=
kInf));
432 const std::vector<double>& z_values,
const double w_value,
433 const double fist_stage_objective) {
434 const int num_facilities = network_.num_facilities();
437 for (
int facility = 0; facility < num_facilities; ++facility) {
438 if (z_values[facility] < -
kZeroTol) {
440 <<
"negative z_value in first stage: " << z_values[facility]
441 <<
" for facility " << facility;
444 const double capacity_value =
std::max(z_values[facility], 0.0);
445 for (
const auto& edge : network_.edges_incident_to_facility(facility)) {
446 second_stage_model_.set_upper_bound(x_.at(edge),
447 location_fraction_ * capacity_value);
449 second_stage_model_.set_upper_bound(supply_constraints_[facility],
455 .parameters = second_stage_params_}));
456 switch (second_stage_result.termination.reason) {
457 case math_opt::TerminationReason::kInfeasible:
462 FeasibilityCut(second_stage_result),
463 _ <<
"on infeasible for second stage solver");
464 return std::make_pair(
kInf, feasibility_cut);
466 case math_opt::TerminationReason::kOptimal:
473 const double upper_bound = fist_stage_objective - w_value +
474 second_stage_result.objective_value();
476 OptimalityCut(second_stage_result),
477 _ <<
"on optimal for second stage solver");
478 return std::make_pair(
upper_bound, optimality_cut);
482 <<
"second stage was not solved to optimality or infeasibility: "
483 << second_stage_result.termination;
502absl::StatusOr<Cut> SecondStageSolver::FeasibilityCut(
504 const int num_facilities = network_.num_facilities();
512 <<
"no dual ray available for feasibility cut";
515 for (
int facility = 0; facility < num_facilities; ++facility) {
517 for (
const auto& edge : network_.edges_incident_to_facility(facility)) {
518 const double reduced_cost =
522 const double dual_value =
523 second_stage_result.
ray_dual_values().at(supply_constraints_[facility]);
527 result.constant = 0.0;
528 for (
const auto& constraint : demand_constraints_) {
529 const double dual_value =
531 result.constant +=
std::max(dual_value, 0.0);
533 result.w_coefficient = 0.0;
553absl::StatusOr<Cut> SecondStageSolver::OptimalityCut(
555 const int num_facilities = network_.num_facilities();
560 <<
"no dual solution available for optimality cut";
563 for (
int facility = 0; facility < num_facilities; ++facility) {
565 for (
const auto& edge : network_.edges_incident_to_facility(facility)) {
566 const double reduced_cost =
571 second_stage_result.
dual_values().at(supply_constraints_[facility]);
575 result.constant = 0.0;
576 for (
const auto& constraint : demand_constraints_) {
577 double dual_value = second_stage_result.
dual_values().at(constraint);
578 result.constant +=
std::max(dual_value, 0.0);
580 result.w_coefficient = 1.0;
584absl::Status Benders(
const FacilityLocationInstance& instance,
585 const double target_precission,
587 const int maximum_iterations = 30000) {
588 const int num_facilities = instance.network.num_facilities();
591 FirstStageProblem first_stage(instance.network, instance.facility_cost);
593 const std::unique_ptr<math_opt::IncrementalSolver> first_stage_solver,
597 SecondStageSolver::New(instance, solver_type));
600 double best_upper_bound =
kInf;
601 std::vector<double> z_values;
602 z_values.resize(num_facilities);
604 LOG(
INFO) <<
"Iteration: " << iteration;
608 first_stage_solver->Solve());
609 if (first_stage_result.termination.reason !=
610 math_opt::TerminationReason::kOptimal) {
612 <<
"could not solve first stage problem to optimality:"
613 << first_stage_result.termination;
615 for (
int j = 0; j < num_facilities; j++) {
616 z_values[j] = first_stage_result.variable_values().at(first_stage.z[j]);
618 const double lower_bound = first_stage_result.objective_value();
623 second_stage_solver->Solve(
624 z_values, first_stage_result.variable_values().at(first_stage.w),
625 first_stage_result.objective_value()));
627 for (
int j = 0; j < num_facilities; j++) {
628 cut_expression += cut.z_coefficients[j] * first_stage.z[j];
630 cut_expression += cut.constant;
631 first_stage.model.AddLinearConstraint(cut_expression <=
632 cut.w_coefficient * first_stage.w);
634 LOG(
INFO) <<
"UB = " << best_upper_bound;
636 if (best_upper_bound -
lower_bound < target_precission) {
637 std::cout <<
"Total iterations = " << iteration << std::endl;
638 std::cout <<
"Final LB = " << absl::StrFormat(
"%.9f",
lower_bound)
640 std::cout <<
"Final UB = " << absl::StrFormat(
"%.9f", best_upper_bound)
644 if (iteration > maximum_iterations) {
648 return absl::OkStatus();
651 const FacilityLocationInstance instance{
652 .network = Network(absl::GetFlag(FLAGS_num_facilities),
653 absl::GetFlag(FLAGS_num_locations),
654 absl::GetFlag(FLAGS_edge_probability)),
655 .location_demand = absl::GetFlag(FLAGS_location_demand),
656 .facility_cost = absl::GetFlag(FLAGS_facility_cost),
657 .location_fraction = absl::GetFlag(FLAGS_location_fraction)};
658 absl::Time
start = absl::Now();
659 RETURN_IF_ERROR(FullProblem(instance, absl::GetFlag(FLAGS_solver_type)))
660 <<
"full solve failed";
661 std::cout <<
"Full solve time : " << absl::Now() -
start << std::endl;
663 RETURN_IF_ERROR(Benders(instance, absl::GetFlag(FLAGS_benders_precission),
664 absl::GetFlag(FLAGS_solver_type)))
665 <<
"Benders solve failed";
666 std::cout <<
"Benders solve time : " << absl::Now() -
start << std::endl;
667 return absl::OkStatus();
671int main(
int argc,
char** argv) {
673 const absl::Status
status = Main();
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
static absl::StatusOr< std::unique_ptr< IncrementalSolver > > New(Model *model, SolverType solver_type, SolverInitArguments arguments={})
int main(int argc, char **argv)
ABSL_FLAG(int, num_facilities, 3000, "Number of facilities.")
void InitGoogle(const char *usage, int *argc, char ***argv, bool deprecated)
constexpr double kZeroTol
LinearExpression Sum(const Iterable &items)
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
StatusBuilder InternalErrorBuilder()
const LinearConstraintMap< double > & dual_values() const
bool has_dual_ray() const
bool has_dual_feasible_solution() const
const LinearConstraintMap< double > & ray_dual_values() const
double objective_value() const
const VariableMap< double > & ray_reduced_costs() const
const VariableMap< double > & reduced_costs() const
#define OR_ASSIGN_OR_RETURN3(lhs, rexpr, error_expression)