20 #include "absl/memory/memory.h"
21 #include "absl/strings/match.h"
22 #include "absl/strings/str_format.h"
34 #ifndef __PORTABLE_PLATFORM__
39 "When true, NaNs and division / zero produce errors. "
40 "This is very useful for debugging, but incompatible with LLVM. "
41 "It is recommended to set this to false for production usage.");
43 "Tells whether do dump the problem to a protobuf file.");
45 "Whether the proto dump file is compressed.");
47 "Whether the proto dump file is binary.");
49 "Number for the dump file, in the form name-000048.pb. "
50 "If < 0, the file is automatically numbered from the number of "
51 "calls to LPSolver::Solve().");
52 DEFINE_string(lp_dump_dir,
"/tmp",
"Directory where dump files are written.");
54 "Base name for dump files. LinearProgram::name_ is used if "
55 "lp_dump_file_basename is empty. If LinearProgram::name_ is "
56 "empty, \"linear_program_dump_file\" is used.");
70 void DumpLinearProgramIfRequiredByFlags(
const LinearProgram& linear_program,
72 if (!FLAGS_lp_dump_to_proto_file)
return;
73 #ifdef __PORTABLE_PLATFORM__
74 LOG(WARNING) <<
"DumpLinearProgramIfRequiredByFlags(linear_program, num) "
75 "requested for linear_program.name()='"
76 << linear_program.name() <<
"', num=" << num
77 <<
" but is not implemented for this platform.";
79 std::string filename = FLAGS_lp_dump_file_basename;
80 if (filename.empty()) {
81 if (linear_program.name().empty()) {
82 filename =
"linear_program_dump";
84 filename = linear_program.name();
88 FLAGS_lp_dump_file_number >= 0 ? FLAGS_lp_dump_file_number : num;
89 absl::StrAppendFormat(&filename,
"-%06d.pb", file_num);
90 const std::string filespec = absl::StrCat(FLAGS_lp_dump_dir,
"/", filename);
97 FLAGS_lp_dump_compressed_file)) {
98 LOG(DFATAL) <<
"Could not write " << filespec;
128 LOG(DFATAL) <<
"SolveWithTimeLimit() called with a nullptr time_limit.";
132 num_revised_simplex_iterations_ = 0;
133 DumpLinearProgramIfRequiredByFlags(lp, num_solves_);
136 LOG(DFATAL) <<
"The columns of the given linear program should be ordered "
137 <<
"by row and contain no zero coefficients. Call CleanUp() "
138 <<
"on it before calling Solve().";
143 LOG(DFATAL) <<
"The given linear program is invalid. It contains NaNs, "
144 <<
"infinite coefficients or invalid bounds specification. "
145 <<
"You can construct it in debug mode to get the exact cause.";
151 <<
"\n******************************************************************"
152 "\n* WARNING: Glop will be very slow because it will use DCHECKs *"
153 "\n* to verify the results and the precision of the solver. *"
154 "\n* You can gain at least an order of magnitude speedup by *"
155 "\n* compiling with optimizations enabled and by defining NDEBUG. *"
156 "\n******************************************************************";
162 if (FLAGS_lp_solver_enable_fp_exceptions) {
179 const bool postsolve_is_needed = preprocessor.
Run(¤t_linear_program_);
181 VLOG(1) <<
"Presolved problem: "
194 RunRevisedSimplexIfNeeded(&solution,
time_limit);
201 VLOG(1) <<
"status: " << status;
204 VLOG(1) <<
"time: " <<
time_limit->GetElapsedTime();
205 VLOG(1) <<
"deterministic_time: "
212 ResizeSolution(RowIndex(0), ColIndex(0));
213 revised_simplex_.reset(
nullptr);
243 if (revised_simplex_ ==
nullptr) {
244 revised_simplex_ = absl::make_unique<RevisedSimplex>();
246 revised_simplex_->LoadStateForNextSolve(state);
247 if (parameters_.use_preprocessing()) {
248 LOG(WARNING) <<
"In GLOP, SetInitialBasis() was called but the parameter "
249 "use_preprocessing is true, this will likely not result in "
272 if (!IsProblemSolutionConsistent(lp, solution)) {
273 VLOG(1) <<
"Inconsistency detected in the solution.";
287 ComputeReducedCosts(lp);
288 const Fractional primal_objective_value = ComputeObjective(lp);
289 const Fractional dual_objective_value = ComputeDualObjective(lp);
290 VLOG(1) <<
"Primal objective (before moving primal/dual values) = "
291 << absl::StrFormat(
"%.15E",
292 ProblemObjectiveValue(lp, primal_objective_value));
293 VLOG(1) <<
"Dual objective (before moving primal/dual values) = "
294 << absl::StrFormat(
"%.15E",
295 ProblemObjectiveValue(lp, dual_objective_value));
299 parameters_.provide_strong_optimal_guarantee()) {
300 MovePrimalValuesWithinBounds(lp);
301 MoveDualValuesWithinBounds(lp);
305 problem_objective_value_ = ProblemObjectiveValue(lp, ComputeObjective(lp));
306 VLOG(1) <<
"Primal objective (after moving primal/dual values) = "
307 << absl::StrFormat(
"%.15E", problem_objective_value_);
309 ComputeReducedCosts(lp);
310 ComputeConstraintActivities(lp);
320 bool rhs_perturbation_is_too_large =
false;
321 bool cost_perturbation_is_too_large =
false;
322 bool primal_infeasibility_is_too_large =
false;
323 bool dual_infeasibility_is_too_large =
false;
324 bool primal_residual_is_too_large =
false;
325 bool dual_residual_is_too_large =
false;
328 ComputeMaxRhsPerturbationToEnforceOptimality(lp,
329 &rhs_perturbation_is_too_large);
330 ComputeMaxCostPerturbationToEnforceOptimality(
331 lp, &cost_perturbation_is_too_large);
332 const double primal_infeasibility =
333 ComputePrimalValueInfeasibility(lp, &primal_infeasibility_is_too_large);
334 const double dual_infeasibility =
335 ComputeDualValueInfeasibility(lp, &dual_infeasibility_is_too_large);
336 const double primal_residual =
337 ComputeActivityInfeasibility(lp, &primal_residual_is_too_large);
338 const double dual_residual =
339 ComputeReducedCostInfeasibility(lp, &dual_residual_is_too_large);
344 max_absolute_primal_infeasibility_ =
345 std::max(primal_infeasibility, primal_residual);
346 max_absolute_dual_infeasibility_ =
347 std::max(dual_infeasibility, dual_residual);
348 VLOG(1) <<
"Max. primal infeasibility = "
349 << max_absolute_primal_infeasibility_;
350 VLOG(1) <<
"Max. dual infeasibility = " << max_absolute_dual_infeasibility_;
355 const double objective_error_ub = ComputeMaxExpectedObjectiveError(lp);
356 VLOG(1) <<
"Objective error <= " << objective_error_ub;
359 parameters_.provide_strong_optimal_guarantee()) {
362 if (primal_infeasibility != 0.0 || dual_infeasibility != 0.0) {
363 LOG(ERROR) <<
"Primal/dual values have been moved to their bounds. "
364 <<
"Therefore the primal/dual infeasibilities should be "
365 <<
"exactly zero (but not the residuals). If this message "
366 <<
"appears, there is probably a bug in "
367 <<
"MovePrimalValuesWithinBounds() or in "
368 <<
"MoveDualValuesWithinBounds().";
370 if (rhs_perturbation_is_too_large) {
371 VLOG(1) <<
"The needed rhs perturbation is too large !!";
374 if (cost_perturbation_is_too_large) {
375 VLOG(1) <<
"The needed cost perturbation is too large !!";
384 if (std::abs(primal_objective_value - dual_objective_value) >
385 objective_error_ub) {
386 VLOG(1) <<
"The objective gap of the final solution is too large.";
392 (primal_residual_is_too_large || primal_infeasibility_is_too_large)) {
393 VLOG(1) <<
"The primal infeasibility of the final solution is too large.";
398 (dual_residual_is_too_large || dual_infeasibility_is_too_large)) {
399 VLOG(1) <<
"The dual infeasibility of the final solution is too large.";
403 may_have_multiple_solutions_ =
408 bool LPSolver::IsOptimalSolutionOnFacet(
const LinearProgram& lp) {
413 const double kReducedCostTolerance = 1e-9;
414 const double kBoundTolerance = 1e-7;
416 for (ColIndex
col(0);
col < num_cols; ++
col) {
422 kReducedCostTolerance) &&
429 for (RowIndex
row(0);
row < num_rows; ++
row) {
435 kReducedCostTolerance) &&
445 return problem_objective_value_;
449 return max_absolute_primal_infeasibility_;
453 return max_absolute_dual_infeasibility_;
457 return may_have_multiple_solutions_;
461 return num_revised_simplex_iterations_;
465 return revised_simplex_ ==
nullptr ? 0.0
466 : revised_simplex_->DeterministicTime();
469 void LPSolver::MovePrimalValuesWithinBounds(
const LinearProgram& lp) {
471 DCHECK_EQ(num_cols, primal_values_.
size());
473 for (ColIndex
col(0);
col < num_cols; ++
col) {
476 DCHECK_LE(lower_bound, upper_bound);
478 error =
std::max(error, primal_values_[
col] - upper_bound);
479 error =
std::max(error, lower_bound - primal_values_[
col]);
483 VLOG(1) <<
"Max. primal values move = " << error;
486 void LPSolver::MoveDualValuesWithinBounds(
const LinearProgram& lp) {
487 const RowIndex num_rows = lp.num_constraints();
488 DCHECK_EQ(num_rows, dual_values_.
size());
489 const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
491 for (RowIndex
row(0);
row < num_rows; ++
row) {
492 const Fractional lower_bound = lp.constraint_lower_bounds()[
row];
493 const Fractional upper_bound = lp.constraint_upper_bounds()[
row];
496 Fractional minimization_dual_value = optimization_sign * dual_values_[
row];
497 if (lower_bound == -
kInfinity && minimization_dual_value > 0.0) {
498 error =
std::max(error, minimization_dual_value);
499 minimization_dual_value = 0.0;
501 if (upper_bound ==
kInfinity && minimization_dual_value < 0.0) {
502 error =
std::max(error, -minimization_dual_value);
503 minimization_dual_value = 0.0;
505 dual_values_[
row] = optimization_sign * minimization_dual_value;
507 VLOG(1) <<
"Max. dual values move = " << error;
510 void LPSolver::ResizeSolution(RowIndex num_rows, ColIndex num_cols) {
511 primal_values_.
resize(num_cols, 0.0);
512 reduced_costs_.
resize(num_cols, 0.0);
515 dual_values_.resize(num_rows, 0.0);
516 constraint_activities_.
resize(num_rows, 0.0);
520 void LPSolver::RunRevisedSimplexIfNeeded(ProblemSolution* solution,
526 if (revised_simplex_ ==
nullptr) {
527 revised_simplex_ = absl::make_unique<RevisedSimplex>();
529 revised_simplex_->SetParameters(parameters_);
530 if (revised_simplex_->Solve(current_linear_program_,
time_limit).ok()) {
531 num_revised_simplex_iterations_ = revised_simplex_->GetNumberOfIterations();
532 solution->status = revised_simplex_->GetProblemStatus();
534 const ColIndex num_cols = revised_simplex_->GetProblemNumCols();
535 DCHECK_EQ(solution->primal_values.size(), num_cols);
536 for (ColIndex
col(0);
col < num_cols; ++
col) {
537 solution->primal_values[
col] = revised_simplex_->GetVariableValue(
col);
538 solution->variable_statuses[
col] =
539 revised_simplex_->GetVariableStatus(
col);
542 const RowIndex num_rows = revised_simplex_->GetProblemNumRows();
543 DCHECK_EQ(solution->dual_values.size(), num_rows);
544 for (RowIndex
row(0);
row < num_rows; ++
row) {
545 solution->dual_values[
row] = revised_simplex_->GetDualValue(
row);
546 solution->constraint_statuses[
row] =
547 revised_simplex_->GetConstraintStatus(
row);
550 VLOG(1) <<
"Error during the revised simplex algorithm.";
560 VLOG(1) <<
"Variable " <<
col <<
" status is "
562 <<
" and its bounds are [" << lb <<
", " << ub <<
"].";
567 VLOG(1) <<
"Constraint " <<
row <<
" status is "
569 <<
", " << ub <<
"].";
574 bool LPSolver::IsProblemSolutionConsistent(
575 const LinearProgram& lp,
const ProblemSolution& solution)
const {
576 const RowIndex num_rows = lp.num_constraints();
577 const ColIndex num_cols = lp.num_variables();
578 if (solution.variable_statuses.size() != num_cols)
return false;
579 if (solution.constraint_statuses.size() != num_rows)
return false;
580 if (solution.primal_values.size() != num_cols)
return false;
581 if (solution.dual_values.size() != num_rows)
return false;
590 RowIndex num_basic_variables(0);
591 for (ColIndex
col(0);
col < num_cols; ++
col) {
596 switch (solution.variable_statuses[
col]) {
600 ++num_basic_variables;
613 LogVariableStatusError(
col,
value, status, lb, ub);
618 if (
value != lb || lb == ub) {
619 LogVariableStatusError(
col,
value, status, lb, ub);
627 LogVariableStatusError(
col,
value, status, lb, ub);
633 LogVariableStatusError(
col,
value, status, lb, ub);
639 for (RowIndex
row(0);
row < num_rows; ++
row) {
650 if (dual_value != 0.0) {
651 VLOG(1) <<
"Constraint " <<
row <<
" is BASIC, but its dual value is "
652 << dual_value <<
" instead of 0.";
655 ++num_basic_variables;
661 if (ub - lb > 1e-12) {
662 LogConstraintStatusError(
row, status, lb, ub);
668 LogConstraintStatusError(
row, status, lb, ub);
674 LogConstraintStatusError(
row, status, lb, ub);
679 if (dual_value != 0.0) {
680 VLOG(1) <<
"Constraint " <<
row <<
" is FREE, but its dual value is "
681 << dual_value <<
" instead of 0.";
685 LogConstraintStatusError(
row, status, lb, ub);
694 if (num_basic_variables != num_rows) {
695 VLOG(1) <<
"Wrong number of basic variables: " << num_basic_variables;
705 Fractional LPSolver::ComputeMaxCostPerturbationToEnforceOptimality(
706 const LinearProgram& lp,
bool* is_too_large) {
708 const ColIndex num_cols = lp.num_variables();
709 const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
710 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
711 for (ColIndex
col(0);
col < num_cols; ++
col) {
715 const Fractional reduced_cost = optimization_sign * reduced_costs_[
col];
720 max_cost_correction =
721 std::max(max_cost_correction, std::abs(reduced_cost));
723 std::abs(reduced_cost) >
724 AllowedError(tolerance, lp.objective_coefficients()[
col]);
727 VLOG(1) <<
"Max. cost perturbation = " << max_cost_correction;
728 return max_cost_correction;
733 Fractional LPSolver::ComputeMaxRhsPerturbationToEnforceOptimality(
734 const LinearProgram& lp,
bool* is_too_large) {
736 const RowIndex num_rows = lp.num_constraints();
737 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
738 for (RowIndex
row(0);
row < num_rows; ++
row) {
739 const Fractional lower_bound = lp.constraint_lower_bounds()[
row];
740 const Fractional upper_bound = lp.constraint_upper_bounds()[
row];
747 rhs_error = std::abs(activity - lower_bound);
748 allowed_error = AllowedError(tolerance, lower_bound);
750 activity > upper_bound) {
751 rhs_error = std::abs(activity - upper_bound);
752 allowed_error = AllowedError(tolerance, upper_bound);
754 max_rhs_correction =
std::max(max_rhs_correction, rhs_error);
755 *is_too_large |= rhs_error > allowed_error;
757 VLOG(1) <<
"Max. rhs perturbation = " << max_rhs_correction;
758 return max_rhs_correction;
761 void LPSolver::ComputeConstraintActivities(
const LinearProgram& lp) {
762 const RowIndex num_rows = lp.num_constraints();
763 const ColIndex num_cols = lp.num_variables();
764 DCHECK_EQ(num_cols, primal_values_.
size());
765 constraint_activities_.
assign(num_rows, 0.0);
766 for (ColIndex
col(0);
col < num_cols; ++
col) {
767 lp.GetSparseColumn(
col).AddMultipleToDenseVector(primal_values_[
col],
768 &constraint_activities_);
772 void LPSolver::ComputeReducedCosts(
const LinearProgram& lp) {
773 const RowIndex num_rows = lp.num_constraints();
774 const ColIndex num_cols = lp.num_variables();
775 DCHECK_EQ(num_rows, dual_values_.size());
776 reduced_costs_.resize(num_cols, 0.0);
777 for (ColIndex
col(0);
col < num_cols; ++
col) {
778 reduced_costs_[
col] = lp.objective_coefficients()[
col] -
783 double LPSolver::ComputeObjective(
const LinearProgram& lp) {
784 const ColIndex num_cols = lp.num_variables();
785 DCHECK_EQ(num_cols, primal_values_.
size());
787 for (ColIndex
col(0);
col < num_cols; ++
col) {
788 sum.
Add(lp.objective_coefficients()[
col] * primal_values_[
col]);
809 double LPSolver::ComputeDualObjective(
const LinearProgram& lp) {
813 const RowIndex num_rows = lp.num_constraints();
814 const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
815 for (RowIndex
row(0);
row < num_rows; ++
row) {
816 const Fractional lower_bound = lp.constraint_lower_bounds()[
row];
817 const Fractional upper_bound = lp.constraint_upper_bounds()[
row];
820 const Fractional corrected_value = optimization_sign * dual_values_[
row];
821 if (corrected_value > 0.0 && lower_bound != -
kInfinity) {
822 dual_objective.Add(dual_values_[
row] * lower_bound);
824 if (corrected_value < 0.0 && upper_bound !=
kInfinity) {
825 dual_objective.Add(dual_values_[
row] * upper_bound);
845 const ColIndex num_cols = lp.num_variables();
846 for (ColIndex
col(0);
col < num_cols; ++
col) {
847 const Fractional lower_bound = lp.variable_lower_bounds()[
col];
848 const Fractional upper_bound = lp.variable_upper_bounds()[
col];
852 const Fractional reduced_cost = optimization_sign * reduced_costs_[
col];
858 reduced_cost > 0.0) {
859 correction = reduced_cost * lower_bound;
861 reduced_cost < 0.0) {
862 correction = reduced_cost * upper_bound;
864 correction = reduced_cost * upper_bound;
867 dual_objective.Add(optimization_sign * correction);
869 return dual_objective.Value();
872 double LPSolver::ComputeMaxExpectedObjectiveError(
const LinearProgram& lp) {
873 const ColIndex num_cols = lp.num_variables();
874 DCHECK_EQ(num_cols, primal_values_.
size());
875 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
877 for (ColIndex
col(0);
col < num_cols; ++
col) {
881 primal_objective_error += std::abs(lp.objective_coefficients()[
col]) *
882 AllowedError(tolerance, primal_values_[
col]);
884 return primal_objective_error;
887 double LPSolver::ComputePrimalValueInfeasibility(
const LinearProgram& lp,
888 bool* is_too_large) {
889 double infeasibility = 0.0;
890 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
891 const ColIndex num_cols = lp.num_variables();
892 for (ColIndex
col(0);
col < num_cols; ++
col) {
893 const Fractional lower_bound = lp.variable_lower_bounds()[
col];
894 const Fractional upper_bound = lp.variable_upper_bounds()[
col];
897 if (lower_bound == upper_bound) {
898 const Fractional error = std::abs(primal_values_[
col] - upper_bound);
899 infeasibility =
std::max(infeasibility, error);
900 *is_too_large |= error > AllowedError(tolerance, upper_bound);
903 if (primal_values_[
col] > upper_bound) {
905 infeasibility =
std::max(infeasibility, error);
906 *is_too_large |= error > AllowedError(tolerance, upper_bound);
908 if (primal_values_[
col] < lower_bound) {
910 infeasibility =
std::max(infeasibility, error);
911 *is_too_large |= error > AllowedError(tolerance, lower_bound);
914 return infeasibility;
917 double LPSolver::ComputeActivityInfeasibility(
const LinearProgram& lp,
918 bool* is_too_large) {
919 double infeasibility = 0.0;
920 int num_problematic_rows(0);
921 const RowIndex num_rows = lp.num_constraints();
922 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
923 for (RowIndex
row(0);
row < num_rows; ++
row) {
925 const Fractional lower_bound = lp.constraint_lower_bounds()[
row];
926 const Fractional upper_bound = lp.constraint_upper_bounds()[
row];
929 if (lower_bound == upper_bound) {
930 if (std::abs(activity - upper_bound) >
931 AllowedError(tolerance, upper_bound)) {
932 VLOG(2) <<
"Row " <<
row.value() <<
" has activity " << activity
933 <<
" which is different from " << upper_bound <<
" by "
934 << activity - upper_bound;
935 ++num_problematic_rows;
937 infeasibility =
std::max(infeasibility, std::abs(activity - upper_bound));
940 if (activity > upper_bound) {
941 const Fractional row_excess = activity - upper_bound;
942 if (row_excess > AllowedError(tolerance, upper_bound)) {
943 VLOG(2) <<
"Row " <<
row.value() <<
" has activity " << activity
944 <<
", exceeding its upper bound " << upper_bound <<
" by "
946 ++num_problematic_rows;
948 infeasibility =
std::max(infeasibility, row_excess);
950 if (activity < lower_bound) {
951 const Fractional row_deficit = lower_bound - activity;
952 if (row_deficit > AllowedError(tolerance, lower_bound)) {
953 VLOG(2) <<
"Row " <<
row.value() <<
" has activity " << activity
954 <<
", below its lower bound " << lower_bound <<
" by "
956 ++num_problematic_rows;
958 infeasibility =
std::max(infeasibility, row_deficit);
961 if (num_problematic_rows > 0) {
962 *is_too_large =
true;
963 VLOG(1) <<
"Number of infeasible rows = " << num_problematic_rows;
965 return infeasibility;
968 double LPSolver::ComputeDualValueInfeasibility(
const LinearProgram& lp,
969 bool* is_too_large) {
970 const Fractional allowed_error = parameters_.solution_feasibility_tolerance();
971 const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
972 double infeasibility = 0.0;
973 const RowIndex num_rows = lp.num_constraints();
974 for (RowIndex
row(0);
row < num_rows; ++
row) {
976 const Fractional lower_bound = lp.constraint_lower_bounds()[
row];
977 const Fractional upper_bound = lp.constraint_upper_bounds()[
row];
979 const Fractional minimization_dual_value = optimization_sign * dual_value;
981 *is_too_large |= minimization_dual_value > allowed_error;
982 infeasibility =
std::max(infeasibility, minimization_dual_value);
985 *is_too_large |= -minimization_dual_value > allowed_error;
986 infeasibility =
std::max(infeasibility, -minimization_dual_value);
989 return infeasibility;
992 double LPSolver::ComputeReducedCostInfeasibility(
const LinearProgram& lp,
993 bool* is_too_large) {
994 const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
995 double infeasibility = 0.0;
996 const ColIndex num_cols = lp.num_variables();
997 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
998 for (ColIndex
col(0);
col < num_cols; ++
col) {
1000 const Fractional lower_bound = lp.variable_lower_bounds()[
col];
1001 const Fractional upper_bound = lp.variable_upper_bounds()[
col];
1004 optimization_sign * reduced_cost;
1006 AllowedError(tolerance, lp.objective_coefficients()[
col]);
1008 *is_too_large |= minimization_reduced_cost > allowed_error;
1009 infeasibility =
std::max(infeasibility, minimization_reduced_cost);
1012 *is_too_large |= -minimization_reduced_cost > allowed_error;
1013 infeasibility =
std::max(infeasibility, -minimization_reduced_cost);
1016 return infeasibility;