20 #include "absl/memory/memory.h"
21 #include "absl/strings/match.h"
22 #include "absl/strings/str_format.h"
34 #ifndef __PORTABLE_PLATFORM__
38 ABSL_FLAG(
bool, lp_solver_enable_fp_exceptions,
false,
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().");
53 "Directory where dump files are written.");
55 "Base name for dump files. LinearProgram::name_ is used if "
56 "lp_dump_file_basename is empty. If LinearProgram::name_ is "
57 "empty, \"linear_program_dump_file\" is used.");
70 void DumpLinearProgramIfRequiredByFlags(
const LinearProgram& linear_program,
72 if (!absl::GetFlag(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 = absl::GetFlag(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();
87 const int file_num = absl::GetFlag(FLAGS_lp_dump_file_number) >= 0
88 ? absl::GetFlag(FLAGS_lp_dump_file_number)
90 absl::StrAppendFormat(&filename,
"-%06d.pb", file_num);
91 const std::string filespec =
92 absl::StrCat(absl::GetFlag(FLAGS_lp_dump_dir),
"/", filename);
95 const ProtoWriteFormat write_format = absl::GetFlag(FLAGS_lp_dump_binary_file)
96 ? ProtoWriteFormat::kProtoBinary
97 : ProtoWriteFormat::kProtoText;
99 absl::GetFlag(FLAGS_lp_dump_compressed_file))) {
100 LOG(DFATAL) <<
"Could not write " << filespec;
130 LOG(DFATAL) <<
"SolveWithTimeLimit() called with a nullptr time_limit.";
134 num_revised_simplex_iterations_ = 0;
135 DumpLinearProgramIfRequiredByFlags(lp, num_solves_);
138 LOG(DFATAL) <<
"The columns of the given linear program should be ordered "
139 <<
"by row and contain no zero coefficients. Call CleanUp() "
140 <<
"on it before calling Solve().";
142 return ProblemStatus::INVALID_PROBLEM;
145 LOG(DFATAL) <<
"The given linear program is invalid. It contains NaNs, "
146 <<
"infinite coefficients or invalid bounds specification. "
147 <<
"You can construct it in debug mode to get the exact cause.";
149 return ProblemStatus::INVALID_PROBLEM;
153 <<
"\n******************************************************************"
154 "\n* WARNING: Glop will be very slow because it will use DCHECKs *"
155 "\n* to verify the results and the precision of the solver. *"
156 "\n* You can gain at least an order of magnitude speedup by *"
157 "\n* compiling with optimizations enabled and by defining NDEBUG. *"
158 "\n******************************************************************";
164 if (absl::GetFlag(FLAGS_lp_solver_enable_fp_exceptions)) {
181 const bool postsolve_is_needed = preprocessor.
Run(¤t_linear_program_);
183 VLOG(1) <<
"Presolved problem: "
196 RunRevisedSimplexIfNeeded(&solution,
time_limit);
203 VLOG(1) <<
"status: " << status;
207 VLOG(1) <<
"deterministic_time: "
214 ResizeSolution(RowIndex(0), ColIndex(0));
215 revised_simplex_.reset(
nullptr);
245 if (revised_simplex_ ==
nullptr) {
246 revised_simplex_ = absl::make_unique<RevisedSimplex>();
248 revised_simplex_->LoadStateForNextSolve(state);
249 if (parameters_.use_preprocessing()) {
250 LOG(
WARNING) <<
"In GLOP, SetInitialBasis() was called but the parameter "
251 "use_preprocessing is true, this will likely not result in "
274 if (!IsProblemSolutionConsistent(lp, solution)) {
275 VLOG(1) <<
"Inconsistency detected in the solution.";
289 ComputeReducedCosts(lp);
290 const Fractional primal_objective_value = ComputeObjective(lp);
291 const Fractional dual_objective_value = ComputeDualObjective(lp);
292 VLOG(1) <<
"Primal objective (before moving primal/dual values) = "
293 << absl::StrFormat(
"%.15E",
294 ProblemObjectiveValue(lp, primal_objective_value));
295 VLOG(1) <<
"Dual objective (before moving primal/dual values) = "
296 << absl::StrFormat(
"%.15E",
297 ProblemObjectiveValue(lp, dual_objective_value));
301 parameters_.provide_strong_optimal_guarantee()) {
302 MovePrimalValuesWithinBounds(lp);
303 MoveDualValuesWithinBounds(lp);
307 problem_objective_value_ = ProblemObjectiveValue(lp, ComputeObjective(lp));
308 VLOG(1) <<
"Primal objective (after moving primal/dual values) = "
309 << absl::StrFormat(
"%.15E", problem_objective_value_);
311 ComputeReducedCosts(lp);
312 ComputeConstraintActivities(lp);
322 bool rhs_perturbation_is_too_large =
false;
323 bool cost_perturbation_is_too_large =
false;
324 bool primal_infeasibility_is_too_large =
false;
325 bool dual_infeasibility_is_too_large =
false;
326 bool primal_residual_is_too_large =
false;
327 bool dual_residual_is_too_large =
false;
330 ComputeMaxRhsPerturbationToEnforceOptimality(lp,
331 &rhs_perturbation_is_too_large);
332 ComputeMaxCostPerturbationToEnforceOptimality(
333 lp, &cost_perturbation_is_too_large);
334 const double primal_infeasibility =
335 ComputePrimalValueInfeasibility(lp, &primal_infeasibility_is_too_large);
336 const double dual_infeasibility =
337 ComputeDualValueInfeasibility(lp, &dual_infeasibility_is_too_large);
338 const double primal_residual =
339 ComputeActivityInfeasibility(lp, &primal_residual_is_too_large);
340 const double dual_residual =
341 ComputeReducedCostInfeasibility(lp, &dual_residual_is_too_large);
346 max_absolute_primal_infeasibility_ =
347 std::max(primal_infeasibility, primal_residual);
348 max_absolute_dual_infeasibility_ =
349 std::max(dual_infeasibility, dual_residual);
350 VLOG(1) <<
"Max. primal infeasibility = "
351 << max_absolute_primal_infeasibility_;
352 VLOG(1) <<
"Max. dual infeasibility = " << max_absolute_dual_infeasibility_;
357 const double objective_error_ub = ComputeMaxExpectedObjectiveError(lp);
358 VLOG(1) <<
"Objective error <= " << objective_error_ub;
361 parameters_.provide_strong_optimal_guarantee()) {
364 if (primal_infeasibility != 0.0 || dual_infeasibility != 0.0) {
365 LOG(
ERROR) <<
"Primal/dual values have been moved to their bounds. "
366 <<
"Therefore the primal/dual infeasibilities should be "
367 <<
"exactly zero (but not the residuals). If this message "
368 <<
"appears, there is probably a bug in "
369 <<
"MovePrimalValuesWithinBounds() or in "
370 <<
"MoveDualValuesWithinBounds().";
372 if (rhs_perturbation_is_too_large) {
373 VLOG(1) <<
"The needed rhs perturbation is too large !!";
376 if (cost_perturbation_is_too_large) {
377 VLOG(1) <<
"The needed cost perturbation is too large !!";
386 if (std::abs(primal_objective_value - dual_objective_value) >
387 objective_error_ub) {
388 VLOG(1) <<
"The objective gap of the final solution is too large.";
393 status == ProblemStatus::PRIMAL_FEASIBLE) &&
394 (primal_residual_is_too_large || primal_infeasibility_is_too_large)) {
395 VLOG(1) <<
"The primal infeasibility of the final solution is too large.";
399 status == ProblemStatus::DUAL_FEASIBLE) &&
400 (dual_residual_is_too_large || dual_infeasibility_is_too_large)) {
401 VLOG(1) <<
"The dual infeasibility of the final solution is too large.";
405 may_have_multiple_solutions_ =
410 bool LPSolver::IsOptimalSolutionOnFacet(
const LinearProgram& lp) {
415 const double kReducedCostTolerance = 1e-9;
416 const double kBoundTolerance = 1e-7;
418 for (ColIndex
col(0);
col < num_cols; ++
col) {
424 kReducedCostTolerance) &&
431 for (RowIndex
row(0);
row < num_rows; ++
row) {
437 kReducedCostTolerance) &&
447 return problem_objective_value_;
451 return max_absolute_primal_infeasibility_;
455 return max_absolute_dual_infeasibility_;
459 return may_have_multiple_solutions_;
463 return num_revised_simplex_iterations_;
467 return revised_simplex_ ==
nullptr ? 0.0
468 : revised_simplex_->DeterministicTime();
471 void LPSolver::MovePrimalValuesWithinBounds(
const LinearProgram& lp) {
475 for (ColIndex
col(0);
col < num_cols; ++
col) {
480 error =
std::max(error, primal_values_[
col] - upper_bound);
481 error =
std::max(error, lower_bound - primal_values_[
col]);
485 VLOG(1) <<
"Max. primal values move = " << error;
488 void LPSolver::MoveDualValuesWithinBounds(
const LinearProgram& lp) {
489 const RowIndex num_rows = lp.num_constraints();
491 const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
493 for (RowIndex
row(0);
row < num_rows; ++
row) {
494 const Fractional lower_bound = lp.constraint_lower_bounds()[
row];
495 const Fractional upper_bound = lp.constraint_upper_bounds()[
row];
498 Fractional minimization_dual_value = optimization_sign * dual_values_[
row];
499 if (lower_bound == -
kInfinity && minimization_dual_value > 0.0) {
500 error =
std::max(error, minimization_dual_value);
501 minimization_dual_value = 0.0;
503 if (upper_bound ==
kInfinity && minimization_dual_value < 0.0) {
504 error =
std::max(error, -minimization_dual_value);
505 minimization_dual_value = 0.0;
507 dual_values_[
row] = optimization_sign * minimization_dual_value;
509 VLOG(1) <<
"Max. dual values move = " << error;
512 void LPSolver::ResizeSolution(RowIndex num_rows, ColIndex num_cols) {
513 primal_values_.
resize(num_cols, 0.0);
514 reduced_costs_.
resize(num_cols, 0.0);
517 dual_values_.resize(num_rows, 0.0);
518 constraint_activities_.
resize(num_rows, 0.0);
522 void LPSolver::RunRevisedSimplexIfNeeded(ProblemSolution* solution,
528 if (revised_simplex_ ==
nullptr) {
529 revised_simplex_ = absl::make_unique<RevisedSimplex>();
531 revised_simplex_->SetParameters(parameters_);
532 if (revised_simplex_->Solve(current_linear_program_,
time_limit).ok()) {
533 num_revised_simplex_iterations_ = revised_simplex_->GetNumberOfIterations();
534 solution->status = revised_simplex_->GetProblemStatus();
536 const ColIndex num_cols = revised_simplex_->GetProblemNumCols();
537 DCHECK_EQ(solution->primal_values.size(), num_cols);
538 for (ColIndex
col(0);
col < num_cols; ++
col) {
539 solution->primal_values[
col] = revised_simplex_->GetVariableValue(
col);
540 solution->variable_statuses[
col] =
541 revised_simplex_->GetVariableStatus(
col);
544 const RowIndex num_rows = revised_simplex_->GetProblemNumRows();
545 DCHECK_EQ(solution->dual_values.size(), num_rows);
546 for (RowIndex
row(0);
row < num_rows; ++
row) {
547 solution->dual_values[
row] = revised_simplex_->GetDualValue(
row);
548 solution->constraint_statuses[
row] =
549 revised_simplex_->GetConstraintStatus(
row);
552 VLOG(1) <<
"Error during the revised simplex algorithm.";
562 VLOG(1) <<
"Variable " <<
col <<
" status is "
564 <<
" and its bounds are [" << lb <<
", " << ub <<
"].";
569 VLOG(1) <<
"Constraint " <<
row <<
" status is "
571 <<
", " << ub <<
"].";
576 bool LPSolver::IsProblemSolutionConsistent(
577 const LinearProgram& lp,
const ProblemSolution& solution)
const {
578 const RowIndex num_rows = lp.num_constraints();
579 const ColIndex num_cols = lp.num_variables();
580 if (solution.variable_statuses.size() != num_cols)
return false;
581 if (solution.constraint_statuses.size() != num_rows)
return false;
582 if (solution.primal_values.size() != num_cols)
return false;
583 if (solution.dual_values.size() != num_rows)
return false;
585 solution.status != ProblemStatus::PRIMAL_FEASIBLE &&
586 solution.status != ProblemStatus::DUAL_FEASIBLE) {
592 RowIndex num_basic_variables(0);
593 for (ColIndex
col(0);
col < num_cols; ++
col) {
598 switch (solution.variable_statuses[
col]) {
602 ++num_basic_variables;
615 LogVariableStatusError(
col,
value, status, lb, ub);
620 if (
value != lb || lb == ub) {
621 LogVariableStatusError(
col,
value, status, lb, ub);
629 LogVariableStatusError(
col,
value, status, lb, ub);
635 LogVariableStatusError(
col,
value, status, lb, ub);
641 for (RowIndex
row(0);
row < num_rows; ++
row) {
652 if (dual_value != 0.0) {
653 VLOG(1) <<
"Constraint " <<
row <<
" is BASIC, but its dual value is "
654 << dual_value <<
" instead of 0.";
657 ++num_basic_variables;
663 if (ub - lb > 1e-12) {
664 LogConstraintStatusError(
row, status, lb, ub);
670 LogConstraintStatusError(
row, status, lb, ub);
676 LogConstraintStatusError(
row, status, lb, ub);
681 if (dual_value != 0.0) {
682 VLOG(1) <<
"Constraint " <<
row <<
" is FREE, but its dual value is "
683 << dual_value <<
" instead of 0.";
687 LogConstraintStatusError(
row, status, lb, ub);
696 if (num_basic_variables != num_rows) {
697 VLOG(1) <<
"Wrong number of basic variables: " << num_basic_variables;
707 Fractional LPSolver::ComputeMaxCostPerturbationToEnforceOptimality(
708 const LinearProgram& lp,
bool* is_too_large) {
710 const ColIndex num_cols = lp.num_variables();
711 const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
712 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
713 for (ColIndex
col(0);
col < num_cols; ++
col) {
717 const Fractional reduced_cost = optimization_sign * reduced_costs_[
col];
722 max_cost_correction =
723 std::max(max_cost_correction, std::abs(reduced_cost));
725 std::abs(reduced_cost) >
726 AllowedError(tolerance, lp.objective_coefficients()[
col]);
729 VLOG(1) <<
"Max. cost perturbation = " << max_cost_correction;
730 return max_cost_correction;
735 Fractional LPSolver::ComputeMaxRhsPerturbationToEnforceOptimality(
736 const LinearProgram& lp,
bool* is_too_large) {
738 const RowIndex num_rows = lp.num_constraints();
739 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
740 for (RowIndex
row(0);
row < num_rows; ++
row) {
741 const Fractional lower_bound = lp.constraint_lower_bounds()[
row];
742 const Fractional upper_bound = lp.constraint_upper_bounds()[
row];
749 rhs_error = std::abs(activity - lower_bound);
750 allowed_error = AllowedError(tolerance, lower_bound);
752 activity > upper_bound) {
753 rhs_error = std::abs(activity - upper_bound);
754 allowed_error = AllowedError(tolerance, upper_bound);
756 max_rhs_correction =
std::max(max_rhs_correction, rhs_error);
757 *is_too_large |= rhs_error > allowed_error;
759 VLOG(1) <<
"Max. rhs perturbation = " << max_rhs_correction;
760 return max_rhs_correction;
763 void LPSolver::ComputeConstraintActivities(
const LinearProgram& lp) {
764 const RowIndex num_rows = lp.num_constraints();
765 const ColIndex num_cols = lp.num_variables();
767 constraint_activities_.
assign(num_rows, 0.0);
768 for (ColIndex
col(0);
col < num_cols; ++
col) {
769 lp.GetSparseColumn(
col).AddMultipleToDenseVector(primal_values_[
col],
770 &constraint_activities_);
774 void LPSolver::ComputeReducedCosts(
const LinearProgram& lp) {
775 const RowIndex num_rows = lp.num_constraints();
776 const ColIndex num_cols = lp.num_variables();
777 DCHECK_EQ(num_rows, dual_values_.size());
778 reduced_costs_.resize(num_cols, 0.0);
779 for (ColIndex
col(0);
col < num_cols; ++
col) {
780 reduced_costs_[
col] = lp.objective_coefficients()[
col] -
785 double LPSolver::ComputeObjective(
const LinearProgram& lp) {
786 const ColIndex num_cols = lp.num_variables();
789 for (ColIndex
col(0);
col < num_cols; ++
col) {
790 sum.
Add(lp.objective_coefficients()[
col] * primal_values_[
col]);
811 double LPSolver::ComputeDualObjective(
const LinearProgram& lp) {
815 const RowIndex num_rows = lp.num_constraints();
816 const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
817 for (RowIndex
row(0);
row < num_rows; ++
row) {
818 const Fractional lower_bound = lp.constraint_lower_bounds()[
row];
819 const Fractional upper_bound = lp.constraint_upper_bounds()[
row];
822 const Fractional corrected_value = optimization_sign * dual_values_[
row];
823 if (corrected_value > 0.0 && lower_bound != -
kInfinity) {
824 dual_objective.Add(dual_values_[
row] * lower_bound);
826 if (corrected_value < 0.0 && upper_bound !=
kInfinity) {
827 dual_objective.Add(dual_values_[
row] * upper_bound);
847 const ColIndex num_cols = lp.num_variables();
848 for (ColIndex
col(0);
col < num_cols; ++
col) {
849 const Fractional lower_bound = lp.variable_lower_bounds()[
col];
850 const Fractional upper_bound = lp.variable_upper_bounds()[
col];
854 const Fractional reduced_cost = optimization_sign * reduced_costs_[
col];
860 reduced_cost > 0.0) {
861 correction = reduced_cost * lower_bound;
863 reduced_cost < 0.0) {
864 correction = reduced_cost * upper_bound;
866 correction = reduced_cost * upper_bound;
869 dual_objective.Add(optimization_sign * correction);
871 return dual_objective.Value();
874 double LPSolver::ComputeMaxExpectedObjectiveError(
const LinearProgram& lp) {
875 const ColIndex num_cols = lp.num_variables();
877 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
879 for (ColIndex
col(0);
col < num_cols; ++
col) {
883 primal_objective_error += std::abs(lp.objective_coefficients()[
col]) *
884 AllowedError(tolerance, primal_values_[
col]);
886 return primal_objective_error;
889 double LPSolver::ComputePrimalValueInfeasibility(
const LinearProgram& lp,
890 bool* is_too_large) {
891 double infeasibility = 0.0;
892 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
893 const ColIndex num_cols = lp.num_variables();
894 for (ColIndex
col(0);
col < num_cols; ++
col) {
895 const Fractional lower_bound = lp.variable_lower_bounds()[
col];
896 const Fractional upper_bound = lp.variable_upper_bounds()[
col];
899 if (lower_bound == upper_bound) {
900 const Fractional error = std::abs(primal_values_[
col] - upper_bound);
901 infeasibility =
std::max(infeasibility, error);
902 *is_too_large |= error > AllowedError(tolerance, upper_bound);
905 if (primal_values_[
col] > upper_bound) {
907 infeasibility =
std::max(infeasibility, error);
908 *is_too_large |= error > AllowedError(tolerance, upper_bound);
910 if (primal_values_[
col] < lower_bound) {
912 infeasibility =
std::max(infeasibility, error);
913 *is_too_large |= error > AllowedError(tolerance, lower_bound);
916 return infeasibility;
919 double LPSolver::ComputeActivityInfeasibility(
const LinearProgram& lp,
920 bool* is_too_large) {
921 double infeasibility = 0.0;
922 int num_problematic_rows(0);
923 const RowIndex num_rows = lp.num_constraints();
924 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
925 for (RowIndex
row(0);
row < num_rows; ++
row) {
927 const Fractional lower_bound = lp.constraint_lower_bounds()[
row];
928 const Fractional upper_bound = lp.constraint_upper_bounds()[
row];
931 if (lower_bound == upper_bound) {
932 if (std::abs(activity - upper_bound) >
933 AllowedError(tolerance, upper_bound)) {
934 VLOG(2) <<
"Row " <<
row.value() <<
" has activity " << activity
935 <<
" which is different from " << upper_bound <<
" by "
936 << activity - upper_bound;
937 ++num_problematic_rows;
939 infeasibility =
std::max(infeasibility, std::abs(activity - upper_bound));
942 if (activity > upper_bound) {
943 const Fractional row_excess = activity - upper_bound;
944 if (row_excess > AllowedError(tolerance, upper_bound)) {
945 VLOG(2) <<
"Row " <<
row.value() <<
" has activity " << activity
946 <<
", exceeding its upper bound " << upper_bound <<
" by "
948 ++num_problematic_rows;
950 infeasibility =
std::max(infeasibility, row_excess);
952 if (activity < lower_bound) {
953 const Fractional row_deficit = lower_bound - activity;
954 if (row_deficit > AllowedError(tolerance, lower_bound)) {
955 VLOG(2) <<
"Row " <<
row.value() <<
" has activity " << activity
956 <<
", below its lower bound " << lower_bound <<
" by "
958 ++num_problematic_rows;
960 infeasibility =
std::max(infeasibility, row_deficit);
963 if (num_problematic_rows > 0) {
964 *is_too_large =
true;
965 VLOG(1) <<
"Number of infeasible rows = " << num_problematic_rows;
967 return infeasibility;
970 double LPSolver::ComputeDualValueInfeasibility(
const LinearProgram& lp,
971 bool* is_too_large) {
972 const Fractional allowed_error = parameters_.solution_feasibility_tolerance();
973 const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
974 double infeasibility = 0.0;
975 const RowIndex num_rows = lp.num_constraints();
976 for (RowIndex
row(0);
row < num_rows; ++
row) {
978 const Fractional lower_bound = lp.constraint_lower_bounds()[
row];
979 const Fractional upper_bound = lp.constraint_upper_bounds()[
row];
981 const Fractional minimization_dual_value = optimization_sign * dual_value;
983 *is_too_large |= minimization_dual_value > allowed_error;
984 infeasibility =
std::max(infeasibility, minimization_dual_value);
987 *is_too_large |= -minimization_dual_value > allowed_error;
988 infeasibility =
std::max(infeasibility, -minimization_dual_value);
991 return infeasibility;
994 double LPSolver::ComputeReducedCostInfeasibility(
const LinearProgram& lp,
995 bool* is_too_large) {
996 const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
997 double infeasibility = 0.0;
998 const ColIndex num_cols = lp.num_variables();
999 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
1000 for (ColIndex
col(0);
col < num_cols; ++
col) {
1002 const Fractional lower_bound = lp.variable_lower_bounds()[
col];
1003 const Fractional upper_bound = lp.variable_upper_bounds()[
col];
1006 optimization_sign * reduced_cost;
1008 AllowedError(tolerance, lp.objective_coefficients()[
col]);
1010 *is_too_large |= minimization_reduced_cost > allowed_error;
1011 infeasibility =
std::max(infeasibility, minimization_reduced_cost);
1014 *is_too_large |= -minimization_reduced_cost > allowed_error;
1015 infeasibility =
std::max(infeasibility, -minimization_reduced_cost);
1018 return infeasibility;