24 #include "absl/strings/str_cat.h"
25 #include "absl/strings/str_format.h"
39 "Display numbers as fractions.");
41 "Stop after first basis has been computed.");
43 "Stop after first phase has been completed.");
44 DEFINE_bool(simplex_display_stats,
false,
"Display algorithm statistics.");
54 explicit Cleanup(std::function<
void()> closure)
55 : closure_(std::move(closure)) {}
56 ~Cleanup() { closure_(); }
59 std::function<void()> closure_;
63 #define DCHECK_COL_BOUNDS(col) \
66 DCHECK_GT(num_cols_, col); \
69 #define DCHECK_ROW_BOUNDS(row) \
72 DCHECK_GT(num_rows_, row); \
89 basis_factorization_(&compact_matrix_, &basis_),
90 variables_info_(compact_matrix_, lower_bound_, upper_bound_),
91 variable_values_(parameters_, compact_matrix_, basis_, variables_info_,
92 basis_factorization_),
93 dual_edge_norms_(basis_factorization_),
94 primal_edge_norms_(compact_matrix_, variables_info_,
95 basis_factorization_),
96 update_row_(compact_matrix_, transposed_matrix_, variables_info_, basis_,
97 basis_factorization_),
98 reduced_costs_(compact_matrix_,
objective_, basis_, variables_info_,
99 basis_factorization_, &random_),
100 entering_variable_(variables_info_, &random_, &reduced_costs_,
101 &primal_edge_norms_),
103 num_feasibility_iterations_(0),
104 num_optimization_iterations_(0),
106 feasibility_time_(0.0),
107 optimization_time_(0.0),
108 last_deterministic_time_update_(0.0),
111 function_stats_(
"SimplexFunctionStats"),
114 feasibility_phase_(true),
126 solution_state_ = state;
127 solution_state_has_been_set_externally_ =
true;
131 notify_that_matrix_is_unchanged_ =
true;
140 "The problem is not in the equations form.");
142 Cleanup update_deterministic_time_on_return(
147 const double start_time =
time_limit->GetElapsedTime();
150 dual_infeasibility_improvement_direction_.
clear();
154 feasibility_phase_ =
true;
156 num_feasibility_iterations_ = 0;
157 num_optimization_iterations_ = 0;
158 feasibility_time_ = 0.0;
159 optimization_time_ = 0.0;
166 solution_state_has_been_set_externally_ =
true;
169 ComputeNumberOfEmptyRows();
170 ComputeNumberOfEmptyColumns();
171 DisplayBasicVariableStatistics();
174 if (FLAGS_simplex_stop_after_first_basis) {
179 const bool use_dual = parameters_.use_dual_simplex();
180 VLOG(1) <<
"------ " << (use_dual ?
"Dual simplex." :
"Primal simplex.");
181 VLOG(1) <<
"The matrix has " << compact_matrix_.
num_rows() <<
" rows, "
182 << compact_matrix_.
num_cols() <<
" columns, "
187 VLOG(1) <<
"------ First phase: feasibility.";
188 entering_variable_.
SetPricingRule(parameters_.feasibility_rule());
190 if (parameters_.perturb_costs_in_dual_simplex()) {
196 DisplayIterationInfo();
198 if (problem_status_ != ProblemStatus::DUAL_INFEASIBLE) {
221 DisplayIterationInfo();
224 if (problem_status_ != ProblemStatus::PRIMAL_INFEASIBLE) {
225 InitializeObjectiveAndTestIfUnchanged(lp);
236 feasibility_phase_ =
false;
237 feasibility_time_ =
time_limit->GetElapsedTime() - start_time;
238 entering_variable_.
SetPricingRule(parameters_.optimization_rule());
239 num_feasibility_iterations_ = num_iterations_;
241 VLOG(1) <<
"------ Second phase: optimization.";
255 for (
int num_optims = 0;
259 num_optims <= parameters_.max_number_of_reoptimizations() &&
260 !objective_limit_reached_ &&
261 (num_iterations_ == 0 ||
262 num_iterations_ < parameters_.max_number_of_iterations()) &&
263 !
time_limit->LimitReached() && !FLAGS_simplex_stop_after_feasibility &&
264 (problem_status_ == ProblemStatus::PRIMAL_FEASIBLE ||
265 problem_status_ == ProblemStatus::DUAL_FEASIBLE);
267 if (problem_status_ == ProblemStatus::PRIMAL_FEASIBLE) {
280 DCHECK(problem_status_ == ProblemStatus::PRIMAL_FEASIBLE ||
281 problem_status_ == ProblemStatus::DUAL_FEASIBLE ||
298 DisplayIterationInfo();
306 if (problem_status_ == ProblemStatus::DUAL_UNBOUNDED) {
307 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
311 VLOG(1) <<
"DUAL_UNBOUNDED was reported, but the residual and/or "
312 <<
"dual infeasibility is above the tolerance";
321 parameters_.solution_feasibility_tolerance();
323 solution_tolerance ||
325 VLOG(1) <<
"OPTIMAL was reported, yet one of the residuals is "
326 "above the solution feasibility tolerance after the "
327 "shift/perturbation are removed.";
328 if (parameters_.change_status_to_imprecise()) {
335 parameters_.primal_feasibility_tolerance();
337 parameters_.dual_feasibility_tolerance();
342 if (primal_infeasibility > primal_tolerance &&
343 dual_infeasibility > dual_tolerance) {
344 VLOG(1) <<
"OPTIMAL was reported, yet both of the infeasibility "
345 "are above the tolerance after the "
346 "shift/perturbation are removed.";
347 if (parameters_.change_status_to_imprecise()) {
350 }
else if (primal_infeasibility > primal_tolerance) {
351 VLOG(1) <<
"Re-optimizing with dual simplex ... ";
352 problem_status_ = ProblemStatus::DUAL_FEASIBLE;
353 }
else if (dual_infeasibility > dual_tolerance) {
354 VLOG(1) <<
"Re-optimizing with primal simplex ... ";
355 problem_status_ = ProblemStatus::PRIMAL_FEASIBLE;
365 if (parameters_.change_status_to_imprecise() &&
366 problem_status_ != ProblemStatus::DUAL_INFEASIBLE) {
367 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
371 }
else if (problem_status_ == ProblemStatus::DUAL_FEASIBLE ||
372 problem_status_ == ProblemStatus::DUAL_UNBOUNDED ||
373 problem_status_ == ProblemStatus::PRIMAL_INFEASIBLE) {
377 }
else if (problem_status_ == ProblemStatus::PRIMAL_FEASIBLE ||
378 problem_status_ == ProblemStatus::PRIMAL_UNBOUNDED ||
379 problem_status_ == ProblemStatus::DUAL_INFEASIBLE) {
388 solution_objective_value_ = ComputeInitialProblemObjectiveValue();
397 if (problem_status_ == ProblemStatus::DUAL_UNBOUNDED ||
398 problem_status_ == ProblemStatus::PRIMAL_UNBOUNDED) {
399 solution_objective_value_ =
400 (problem_status_ == ProblemStatus::DUAL_UNBOUNDED) ?
kInfinity
403 solution_objective_value_ = -solution_objective_value_;
407 total_time_ =
time_limit->GetElapsedTime() - start_time;
408 optimization_time_ = total_time_ - feasibility_time_;
409 num_optimization_iterations_ = num_iterations_ - num_feasibility_iterations_;
416 return problem_status_;
420 return solution_objective_value_;
430 return variable_values_.
Get(
col);
434 return solution_reduced_costs_[
col];
438 return solution_reduced_costs_;
442 return solution_dual_values_[
row];
454 return -variable_values_.
Get(SlackColIndex(
row));
471 DCHECK_EQ(problem_status_, ProblemStatus::PRIMAL_UNBOUNDED);
472 return solution_primal_ray_;
475 DCHECK_EQ(problem_status_, ProblemStatus::DUAL_UNBOUNDED);
476 return solution_dual_ray_;
480 DCHECK_EQ(problem_status_, ProblemStatus::DUAL_UNBOUNDED);
481 return solution_dual_ray_row_combination_;
488 return basis_factorization_;
491 std::string RevisedSimplex::GetPrettySolverStats()
const {
492 return absl::StrFormat(
493 "Problem status : %s\n"
494 "Solving time : %-6.4g\n"
495 "Number of iterations : %u\n"
496 "Time for solvability (first phase) : %-6.4g\n"
497 "Number of iterations for solvability : %u\n"
498 "Time for optimization : %-6.4g\n"
499 "Number of iterations for optimization : %u\n"
500 "Stop after first basis : %d\n",
502 feasibility_time_, num_feasibility_iterations_, optimization_time_,
503 num_optimization_iterations_, FLAGS_simplex_stop_after_first_basis);
514 void RevisedSimplex::SetVariableNames() {
515 variable_name_.
resize(num_cols_,
"");
516 for (ColIndex
col(0);
col < first_slack_col_; ++
col) {
517 const ColIndex var_index =
col + 1;
520 for (ColIndex
col(first_slack_col_);
col < num_cols_; ++
col) {
521 const ColIndex var_index =
col - first_slack_col_ + 1;
527 ColIndex
col)
const {
529 if (lower_bound_[
col] == upper_bound_[
col]) {
539 return std::abs(lower_bound_[
col]) <= std::abs(upper_bound_[
col])
544 void RevisedSimplex::SetNonBasicVariableStatusAndDeriveValue(
550 bool RevisedSimplex::BasisIsConsistent()
const {
553 for (RowIndex
row(0);
row < num_rows_; ++
row) {
554 const ColIndex
col = basis_[
row];
555 if (!is_basic.IsSet(
col))
return false;
558 ColIndex cols_in_basis(0);
559 ColIndex cols_not_in_basis(0);
560 for (ColIndex
col(0);
col < num_cols_; ++
col) {
561 cols_in_basis += is_basic.IsSet(
col);
562 cols_not_in_basis += !is_basic.IsSet(
col);
563 if (is_basic.IsSet(
col) !=
569 if (cols_not_in_basis != num_cols_ -
RowToColIndex(num_rows_))
return false;
575 void RevisedSimplex::UpdateBasis(ColIndex entering_col, RowIndex basis_row,
584 DCHECK_NE(basis_[basis_row], entering_col);
587 const ColIndex leaving_col = basis_[basis_row];
593 variables_info_.
Update(leaving_col, leaving_variable_status);
598 basis_[basis_row] = entering_col;
606 class ColumnComparator {
609 bool operator()(ColIndex col_a, ColIndex col_b)
const {
610 return value_[col_a] < value_[col_b];
627 void RevisedSimplex::UseSingletonColumnInInitialBasis(
RowToColMapping* basis) {
634 std::vector<ColIndex> singleton_column;
635 DenseRow cost_variation(num_cols_, 0.0);
636 for (ColIndex
col(0);
col < num_cols_; ++
col) {
638 if (lower_bound_[
col] == upper_bound_[
col])
continue;
640 if (variable_values_.
Get(
col) == lower_bound_[
col]) {
641 cost_variation[
col] = objective_[
col] / std::abs(slope);
643 cost_variation[
col] = -objective_[
col] / std::abs(slope);
645 singleton_column.push_back(
col);
647 if (singleton_column.empty())
return;
654 ColumnComparator comparator(cost_variation);
655 std::sort(singleton_column.begin(), singleton_column.end(), comparator);
656 DCHECK_LE(cost_variation[singleton_column.front()],
657 cost_variation[singleton_column.back()]);
665 for (
const ColIndex
col : singleton_column) {
677 if (error_[
row] == 0.0)
continue;
685 if (new_value >= lower_bound_[
col] && new_value <= upper_bound_[
col]) {
697 DCHECK_NE(box_width, 0.0);
698 DCHECK_NE(error_[
row], 0.0);
700 if (variable_values[
col] == lower_bound_[
col] && error_sign > 0.0) {
702 error_[
row] -= coeff * box_width;
703 SetNonBasicVariableStatusAndDeriveValue(
col,
707 if (variable_values[
col] == upper_bound_[
col] && error_sign < 0.0) {
709 error_[
row] += coeff * box_width;
710 SetNonBasicVariableStatusAndDeriveValue(
col,
717 bool RevisedSimplex::InitializeMatrixAndTestIfUnchanged(
718 const LinearProgram& lp,
bool* only_change_is_new_rows,
719 bool* only_change_is_new_cols, ColIndex* num_new_cols) {
721 DCHECK(only_change_is_new_rows !=
nullptr);
722 DCHECK(only_change_is_new_cols !=
nullptr);
723 DCHECK(num_new_cols !=
nullptr);
724 DCHECK_NE(
kInvalidCol, lp.GetFirstSlackVariable());
725 DCHECK_EQ(num_cols_, compact_matrix_.
num_cols());
726 DCHECK_EQ(num_rows_, compact_matrix_.
num_rows());
728 DCHECK_EQ(lp.num_variables(),
729 lp.GetFirstSlackVariable() +
RowToColIndex(lp.num_constraints()));
731 const bool old_part_of_matrix_is_unchanged =
733 num_rows_, first_slack_col_, lp.GetSparseMatrix(), compact_matrix_);
738 if (old_part_of_matrix_is_unchanged && lp.num_constraints() == num_rows_ &&
739 lp.num_variables() == num_cols_) {
745 *only_change_is_new_rows = old_part_of_matrix_is_unchanged &&
746 lp.num_constraints() > num_rows_ &&
747 lp.GetFirstSlackVariable() == first_slack_col_;
751 *only_change_is_new_cols = old_part_of_matrix_is_unchanged &&
752 lp.num_constraints() == num_rows_ &&
753 lp.GetFirstSlackVariable() > first_slack_col_;
755 *only_change_is_new_cols ? lp.num_variables() - num_cols_ : ColIndex(0);
758 first_slack_col_ = lp.GetFirstSlackVariable();
761 num_rows_ = lp.num_constraints();
762 num_cols_ = lp.num_variables();
769 if (parameters_.use_transposed_matrix()) {
775 bool RevisedSimplex::OldBoundsAreUnchangedAndNewVariablesHaveOneBoundAtZero(
776 const LinearProgram& lp, ColIndex num_new_cols) {
778 DCHECK_EQ(lp.num_variables(), num_cols_);
779 DCHECK_LE(num_new_cols, first_slack_col_);
780 const ColIndex first_new_col(first_slack_col_ - num_new_cols);
783 for (ColIndex
col(0);
col < first_new_col; ++
col) {
784 if (lower_bound_[
col] != lp.variable_lower_bounds()[
col] ||
785 upper_bound_[
col] != lp.variable_upper_bounds()[
col]) {
790 for (ColIndex
col(first_new_col);
col < first_slack_col_; ++
col) {
791 if (lp.variable_lower_bounds()[
col] != 0.0 &&
792 lp.variable_upper_bounds()[
col] != 0.0) {
797 for (ColIndex
col(first_slack_col_);
col < num_cols_; ++
col) {
798 if (lower_bound_[
col - num_new_cols] != lp.variable_lower_bounds()[
col] ||
799 upper_bound_[
col - num_new_cols] != lp.variable_upper_bounds()[
col]) {
806 bool RevisedSimplex::InitializeBoundsAndTestIfUnchanged(
807 const LinearProgram& lp) {
809 lower_bound_.
resize(num_cols_, 0.0);
810 upper_bound_.
resize(num_cols_, 0.0);
814 bool bounds_are_unchanged =
true;
815 DCHECK_EQ(lp.num_variables(), num_cols_);
816 for (ColIndex
col(0);
col < lp.num_variables(); ++
col) {
817 if (lower_bound_[
col] != lp.variable_lower_bounds()[
col] ||
818 upper_bound_[
col] != lp.variable_upper_bounds()[
col]) {
819 bounds_are_unchanged =
false;
823 if (!bounds_are_unchanged) {
824 lower_bound_ = lp.variable_lower_bounds();
825 upper_bound_ = lp.variable_upper_bounds();
827 return bounds_are_unchanged;
830 bool RevisedSimplex::InitializeObjectiveAndTestIfUnchanged(
831 const LinearProgram& lp) {
834 bool objective_is_unchanged =
true;
835 objective_.
resize(num_cols_, 0.0);
836 DCHECK_EQ(num_cols_, lp.num_variables());
837 if (lp.IsMaximizationProblem()) {
839 for (ColIndex
col(0);
col < lp.num_variables(); ++
col) {
841 if (objective_[
col] != coeff) {
842 objective_is_unchanged =
false;
844 objective_[
col] = coeff;
846 objective_offset_ = -lp.objective_offset();
847 objective_scaling_factor_ = -lp.objective_scaling_factor();
849 for (ColIndex
col(0);
col < lp.num_variables(); ++
col) {
850 if (objective_[
col] != lp.objective_coefficients()[
col]) {
851 objective_is_unchanged =
false;
855 if (!objective_is_unchanged) {
856 objective_ = lp.objective_coefficients();
858 objective_offset_ = lp.objective_offset();
859 objective_scaling_factor_ = lp.objective_scaling_factor();
861 return objective_is_unchanged;
864 void RevisedSimplex::InitializeObjectiveLimit(
const LinearProgram& lp) {
865 objective_limit_reached_ =
false;
866 DCHECK(std::isfinite(objective_offset_));
867 DCHECK(std::isfinite(objective_scaling_factor_));
868 DCHECK_NE(0.0, objective_scaling_factor_);
871 const Fractional tolerance = parameters_.solution_feasibility_tolerance();
872 for (
const bool set_dual : {
true,
false}) {
884 const Fractional limit = (objective_scaling_factor_ >= 0.0) != set_dual
885 ? parameters_.objective_lower_limit()
886 : parameters_.objective_upper_limit();
888 limit / objective_scaling_factor_ - objective_offset_;
893 dual_objective_limit_ = std::isfinite(shifted_limit)
894 ? shifted_limit * (1.0 + tolerance)
897 primal_objective_limit_ = std::isfinite(shifted_limit)
898 ? shifted_limit * (1.0 - tolerance)
904 void RevisedSimplex::InitializeVariableStatusesForWarmStart(
905 const BasisState& state, ColIndex num_new_cols) {
907 RowIndex num_basic_variables(0);
908 DCHECK_LE(num_new_cols, first_slack_col_);
909 const ColIndex first_new_col(first_slack_col_ - num_new_cols);
912 for (ColIndex
col(0);
col < num_cols_; ++
col) {
917 if (
col < first_new_col &&
col < state.statuses.size()) {
918 status = state.statuses[
col];
919 }
else if (
col >= first_slack_col_ &&
920 col - num_new_cols < state.statuses.size()) {
921 status = state.statuses[
col - num_new_cols];
926 if (num_basic_variables == num_rows_) {
927 VLOG(1) <<
"Too many basic variables in the warm-start basis."
928 <<
"Only keeping the first ones as VariableStatus::BASIC.";
931 ++num_basic_variables;
938 if ((status != default_status) &&
946 status = default_status;
961 Status RevisedSimplex::CreateInitialBasis() {
968 int num_free_variables = 0;
970 for (ColIndex
col(0);
col < num_cols_; ++
col) {
972 SetNonBasicVariableStatusAndDeriveValue(
col, status);
975 VLOG(1) <<
"Number of free variables in the problem: " << num_free_variables;
979 for (RowIndex
row(0);
row < num_rows_; ++
row) {
980 basis[
row] = SlackColIndex(
row);
985 if (!parameters_.use_dual_simplex() &&
986 parameters_.initial_basis() != GlopParameters::MAROS &&
987 parameters_.exploit_singleton_column_in_initial_basis()) {
991 for (ColIndex
col(0);
col < num_cols_; ++
col) {
995 if (objective > 0 &&
IsFinite(lower_bound_[
col]) &&
997 SetNonBasicVariableStatusAndDeriveValue(
col,
999 }
else if (objective < 0 &&
IsFinite(upper_bound_[
col]) &&
1001 SetNonBasicVariableStatusAndDeriveValue(
col,
1008 ComputeVariableValuesError();
1017 UseSingletonColumnInInitialBasis(&basis);
1020 for (RowIndex
row(0);
row < num_rows_; ++
row) {
1022 basis[
row] = SlackColIndex(
row);
1028 if (parameters_.initial_basis() == GlopParameters::NONE) {
1029 return InitializeFirstBasis(basis);
1031 if (parameters_.initial_basis() == GlopParameters::MAROS) {
1032 InitialBasis initial_basis(compact_matrix_, objective_, lower_bound_,
1034 if (parameters_.use_dual_simplex()) {
1037 initial_basis.GetDualMarosBasis(num_cols_, &basis);
1039 initial_basis.GetPrimalMarosBasis(num_cols_, &basis);
1041 int number_changed = 0;
1042 for (RowIndex
row(0);
row < num_rows_; ++
row) {
1043 if (basis[
row] != SlackColIndex(
row)) {
1047 VLOG(1) <<
"Number of Maros basis changes: " << number_changed;
1048 }
else if (parameters_.initial_basis() == GlopParameters::BIXBY ||
1049 parameters_.initial_basis() == GlopParameters::TRIANGULAR) {
1051 int num_fixed_variables = 0;
1052 for (RowIndex
row(0);
row < basis.size(); ++
row) {
1053 const ColIndex
col = basis[
row];
1054 if (lower_bound_[
col] == upper_bound_[
col]) {
1056 ++num_fixed_variables;
1060 if (num_fixed_variables == 0) {
1061 VLOG(1) <<
"Crash is set to " << parameters_.initial_basis()
1062 <<
" but there is no equality rows to remove from initial all "
1066 VLOG(1) <<
"Trying to remove " << num_fixed_variables
1067 <<
" fixed variables from the initial basis.";
1068 InitialBasis initial_basis(compact_matrix_, objective_, lower_bound_,
1071 if (parameters_.initial_basis() == GlopParameters::BIXBY) {
1072 if (parameters_.use_scaling()) {
1073 initial_basis.CompleteBixbyBasis(first_slack_col_, &basis);
1075 VLOG(1) <<
"Bixby initial basis algorithm requires the problem "
1076 <<
"to be scaled. Skipping Bixby's algorithm.";
1078 }
else if (parameters_.initial_basis() == GlopParameters::TRIANGULAR) {
1081 if (parameters_.use_dual_simplex()) {
1084 initial_basis.CompleteTriangularDualBasis(num_cols_, &basis);
1086 initial_basis.CompleteTriangularPrimalBasis(num_cols_, &basis);
1089 const Status status = InitializeFirstBasis(basis);
1093 VLOG(1) <<
"Reverting to all slack basis.";
1095 for (RowIndex
row(0);
row < num_rows_; ++
row) {
1096 basis[
row] = SlackColIndex(
row);
1102 LOG(WARNING) <<
"Unsupported initial_basis parameters: "
1103 << parameters_.initial_basis();
1106 return InitializeFirstBasis(basis);
1109 Status RevisedSimplex::InitializeFirstBasis(
const RowToColMapping& basis) {
1115 for (RowIndex
row(0);
row < num_rows_; ++
row) {
1117 basis_[
row] = SlackColIndex(
row);
1131 if (condition_number_ub > parameters_.initial_condition_number_threshold()) {
1132 const std::string error_message =
1133 absl::StrCat(
"The matrix condition number upper bound is too high: ",
1134 condition_number_ub);
1135 VLOG(1) << error_message;
1140 for (RowIndex
row(0);
row < num_rows_; ++
row) {
1143 DCHECK(BasisIsConsistent());
1149 if (VLOG_IS_ON(1)) {
1150 const Fractional tolerance = parameters_.primal_feasibility_tolerance();
1152 VLOG(1) << absl::StrCat(
1153 "The primal residual of the initial basis is above the tolerance, ",
1160 Status RevisedSimplex::Initialize(
const LinearProgram& lp) {
1161 parameters_ = initial_parameters_;
1162 PropagateParameters();
1169 ColIndex num_new_cols(0);
1170 bool only_change_is_new_rows =
false;
1171 bool only_change_is_new_cols =
false;
1172 bool matrix_is_unchanged =
true;
1173 bool only_new_bounds =
false;
1174 if (solution_state_.
IsEmpty() || !notify_that_matrix_is_unchanged_) {
1175 matrix_is_unchanged = InitializeMatrixAndTestIfUnchanged(
1176 lp, &only_change_is_new_rows, &only_change_is_new_cols, &num_new_cols);
1177 only_new_bounds = only_change_is_new_cols && num_new_cols > 0 &&
1178 OldBoundsAreUnchangedAndNewVariablesHaveOneBoundAtZero(
1181 CHECK(InitializeMatrixAndTestIfUnchanged(
1182 lp, &only_change_is_new_rows, &only_change_is_new_cols, &num_new_cols));
1184 notify_that_matrix_is_unchanged_ =
false;
1185 const bool objective_is_unchanged = InitializeObjectiveAndTestIfUnchanged(lp);
1186 const bool bounds_are_unchanged = InitializeBoundsAndTestIfUnchanged(lp);
1191 if (matrix_is_unchanged && parameters_.allow_simplex_algorithm_change()) {
1192 if (objective_is_unchanged && !bounds_are_unchanged) {
1193 parameters_.set_use_dual_simplex(
true);
1194 PropagateParameters();
1196 if (bounds_are_unchanged && !objective_is_unchanged) {
1197 parameters_.set_use_dual_simplex(
false);
1198 PropagateParameters();
1202 InitializeObjectiveLimit(lp);
1207 if (VLOG_IS_ON(1)) {
1218 bool solve_from_scratch =
true;
1221 if (!solution_state_.
IsEmpty() && !solution_state_has_been_set_externally_) {
1222 if (!parameters_.use_dual_simplex()) {
1227 dual_edge_norms_.
Clear();
1228 dual_pricing_vector_.
clear();
1229 if (matrix_is_unchanged && bounds_are_unchanged) {
1233 solve_from_scratch =
false;
1234 }
else if (only_change_is_new_cols && only_new_bounds) {
1235 InitializeVariableStatusesForWarmStart(solution_state_, num_new_cols);
1236 const ColIndex first_new_col(first_slack_col_ - num_new_cols);
1237 for (ColIndex& col_ref : basis_) {
1238 if (col_ref >= first_new_col) {
1239 col_ref += num_new_cols;
1246 primal_edge_norms_.
Clear();
1248 solve_from_scratch =
false;
1254 primal_edge_norms_.
Clear();
1255 if (objective_is_unchanged) {
1256 if (matrix_is_unchanged) {
1257 if (!bounds_are_unchanged) {
1258 InitializeVariableStatusesForWarmStart(solution_state_,
1262 solve_from_scratch =
false;
1263 }
else if (only_change_is_new_rows) {
1266 InitializeVariableStatusesForWarmStart(solution_state_, ColIndex(0));
1273 dual_pricing_vector_.
clear();
1276 if (InitializeFirstBasis(basis_).ok()) {
1277 solve_from_scratch =
false;
1286 if (solve_from_scratch && !solution_state_.
IsEmpty()) {
1289 InitializeVariableStatusesForWarmStart(solution_state_, ColIndex(0));
1297 basis_factorization_.
Clear();
1299 primal_edge_norms_.
Clear();
1300 dual_edge_norms_.
Clear();
1301 dual_pricing_vector_.
clear();
1306 if (InitializeFirstBasis(basis_).ok()) {
1307 solve_from_scratch =
false;
1309 VLOG(1) <<
"RevisedSimplex is not using the warm start "
1310 "basis because it is not factorizable.";
1314 if (solve_from_scratch) {
1315 VLOG(1) <<
"Solve from scratch.";
1316 basis_factorization_.
Clear();
1318 primal_edge_norms_.
Clear();
1319 dual_edge_norms_.
Clear();
1320 dual_pricing_vector_.
clear();
1323 VLOG(1) <<
"Incremental solve.";
1325 DCHECK(BasisIsConsistent());
1329 void RevisedSimplex::DisplayBasicVariableStatistics() {
1332 int num_fixed_variables = 0;
1333 int num_free_variables = 0;
1334 int num_variables_at_bound = 0;
1335 int num_slack_variables = 0;
1336 int num_infeasible_variables = 0;
1340 const Fractional tolerance = parameters_.primal_feasibility_tolerance();
1341 for (RowIndex
row(0);
row < num_rows_; ++
row) {
1342 const ColIndex
col = basis_[
row];
1344 if (variable_types[
col] == VariableType::UNCONSTRAINED) {
1345 ++num_free_variables;
1347 if (
value > upper_bound_[
col] + tolerance ||
1348 value < lower_bound_[
col] - tolerance) {
1349 ++num_infeasible_variables;
1351 if (
col >= first_slack_col_) {
1352 ++num_slack_variables;
1354 if (lower_bound_[
col] == upper_bound_[
col]) {
1355 ++num_fixed_variables;
1356 }
else if (variable_values[
col] == lower_bound_[
col] ||
1357 variable_values[
col] == upper_bound_[
col]) {
1358 ++num_variables_at_bound;
1362 VLOG(1) <<
"Basis size: " << num_rows_;
1363 VLOG(1) <<
"Number of basic infeasible variables: "
1364 << num_infeasible_variables;
1365 VLOG(1) <<
"Number of basic slack variables: " << num_slack_variables;
1366 VLOG(1) <<
"Number of basic variables at bound: " << num_variables_at_bound;
1367 VLOG(1) <<
"Number of basic fixed variables: " << num_fixed_variables;
1368 VLOG(1) <<
"Number of basic free variables: " << num_free_variables;
1371 void RevisedSimplex::SaveState() {
1374 solution_state_has_been_set_externally_ =
false;
1377 RowIndex RevisedSimplex::ComputeNumberOfEmptyRows() {
1379 for (ColIndex
col(0);
col < num_cols_; ++
col) {
1381 contains_data[e.row()] =
true;
1384 RowIndex num_empty_rows(0);
1385 for (RowIndex
row(0);
row < num_rows_; ++
row) {
1386 if (!contains_data[
row]) {
1388 VLOG(1) <<
"Row " <<
row <<
" is empty.";
1391 return num_empty_rows;
1394 ColIndex RevisedSimplex::ComputeNumberOfEmptyColumns() {
1395 ColIndex num_empty_cols(0);
1396 for (ColIndex
col(0);
col < num_cols_; ++
col) {
1399 VLOG(1) <<
"Column " <<
col <<
" is empty.";
1402 return num_empty_cols;
1405 void RevisedSimplex::CorrectErrorsOnVariableValues() {
1417 if (primal_residual >= parameters_.harris_tolerance_ratio() *
1418 parameters_.primal_feasibility_tolerance()) {
1420 VLOG(1) <<
"Primal infeasibility (bounds error) = "
1422 <<
", Primal residual |A.x - b| = "
1435 (!feasibility_phase_ && num_consecutive_degenerate_iterations_ >= 100)) {
1436 VLOG(1) <<
"Perturbing the problem.";
1437 const Fractional tolerance = parameters_.harris_tolerance_ratio() *
1438 parameters_.primal_feasibility_tolerance();
1439 std::uniform_real_distribution<double> dist(0, tolerance);
1440 for (ColIndex
col(0);
col < num_cols_; ++
col) {
1441 bound_perturbation_[
col] += dist(random_);
1446 void RevisedSimplex::ComputeVariableValuesError() {
1450 for (ColIndex
col(0);
col < num_cols_; ++
col) {
1456 void RevisedSimplex::ComputeDirection(ColIndex
col) {
1460 direction_infinity_norm_ = 0.0;
1463 for (RowIndex
row(0);
row < num_rows_; ++
row) {
1467 direction_infinity_norm_ =
1472 for (
const auto e : direction_) {
1473 direction_infinity_norm_ =
1474 std::max(direction_infinity_norm_, std::abs(e.coefficient()));
1478 num_rows_ == 0 ? 0.0
1479 :
static_cast<double>(direction_.non_zeros.size()) /
1480 static_cast<double>(num_rows_.value())));
1483 Fractional RevisedSimplex::ComputeDirectionError(ColIndex
col) {
1486 for (
const auto e : direction_) {
1493 template <
bool is_entering_reduced_cost_positive>
1495 const ColIndex
col = basis_[
row];
1499 DCHECK_NE(direction, 0.0);
1500 if (is_entering_reduced_cost_positive) {
1501 if (direction > 0.0) {
1502 return (upper_bound_[
col] -
value) / direction;
1504 return (lower_bound_[
col] -
value) / direction;
1507 if (direction > 0.0) {
1508 return (
value - lower_bound_[
col]) / direction;
1510 return (
value - upper_bound_[
col]) / direction;
1515 template <
bool is_entering_reduced_cost_positive>
1516 Fractional RevisedSimplex::ComputeHarrisRatioAndLeavingCandidates(
1517 Fractional bound_flip_ratio, SparseColumn* leaving_candidates)
const {
1520 parameters_.harris_tolerance_ratio() *
1521 parameters_.primal_feasibility_tolerance();
1522 const Fractional minimum_delta = parameters_.degenerate_ministep_factor() *
1523 parameters_.primal_feasibility_tolerance();
1529 leaving_candidates->Clear();
1536 ? parameters_.minimum_acceptable_pivot()
1537 : parameters_.ratio_test_zero_threshold();
1539 for (
const auto e : direction_) {
1540 const Fractional magnitude = std::abs(e.coefficient());
1541 if (magnitude <= threshold)
continue;
1542 Fractional ratio = GetRatio<is_entering_reduced_cost_positive>(e.row());
1545 if (
false &&
ratio < 0.0) {
1548 ratio += std::abs(bound_perturbation_[basis_[e.row()]] / e.coefficient());
1550 if (
ratio <= harris_ratio) {
1551 leaving_candidates->SetCoefficient(e.row(),
ratio);
1563 harris_ratio =
std::min(harris_ratio,
1564 std::max(minimum_delta / magnitude,
1565 ratio + harris_tolerance / magnitude));
1568 return harris_ratio;
1581 if (current >= 0.0) {
1582 return candidate >= 0.0 && candidate <= current;
1584 return candidate >= current;
1592 Status RevisedSimplex::ChooseLeavingVariableRow(
1593 ColIndex entering_col,
Fractional reduced_cost,
bool* refactorize,
1600 DCHECK_NE(0.0, reduced_cost);
1603 int stats_num_leaving_choices = 0;
1604 equivalent_leaving_choices_.clear();
1606 stats_num_leaving_choices = 0;
1610 const Fractional entering_value = variable_values_.
Get(entering_col);
1612 (reduced_cost > 0.0) ? entering_value - lower_bound_[entering_col]
1613 : upper_bound_[entering_col] - entering_value;
1614 DCHECK_GT(current_ratio, 0.0);
1620 (reduced_cost > 0.0) ? ComputeHarrisRatioAndLeavingCandidates<true>(
1621 current_ratio, &leaving_candidates_)
1622 : ComputeHarrisRatioAndLeavingCandidates<false>(
1623 current_ratio, &leaving_candidates_);
1628 if (current_ratio <= harris_ratio) {
1630 *step_length = current_ratio;
1640 stats_num_leaving_choices = 0;
1642 equivalent_leaving_choices_.clear();
1645 if (
ratio > harris_ratio)
continue;
1646 ++stats_num_leaving_choices;
1647 const RowIndex
row = e.row();
1652 const Fractional candidate_magnitude = std::abs(direction_[
row]);
1653 if (candidate_magnitude < pivot_magnitude)
continue;
1654 if (candidate_magnitude == pivot_magnitude) {
1655 if (!IsRatioMoreOrEquallyStable(
ratio, current_ratio))
continue;
1656 if (
ratio == current_ratio) {
1658 equivalent_leaving_choices_.push_back(
row);
1662 equivalent_leaving_choices_.clear();
1663 current_ratio =
ratio;
1664 pivot_magnitude = candidate_magnitude;
1669 if (!equivalent_leaving_choices_.empty()) {
1670 equivalent_leaving_choices_.push_back(*leaving_row);
1672 equivalent_leaving_choices_[std::uniform_int_distribution<int>(
1673 0, equivalent_leaving_choices_.size() - 1)(random_)];
1685 if (current_ratio <= 0.0) {
1689 parameters_.degenerate_ministep_factor() *
1690 parameters_.primal_feasibility_tolerance();
1691 *step_length = minimum_delta / pivot_magnitude;
1693 *step_length = current_ratio;
1700 TestPivot(entering_col, *leaving_row);
1713 if (pivot_magnitude <
1714 parameters_.small_pivot_threshold() * direction_infinity_norm_) {
1719 VLOG(1) <<
"Refactorizing to avoid pivoting by "
1720 << direction_[*leaving_row]
1721 <<
" direction_infinity_norm_ = " << direction_infinity_norm_
1722 <<
" reduced cost = " << reduced_cost;
1723 *refactorize =
true;
1733 VLOG(1) <<
"Couldn't avoid pivoting by " << direction_[*leaving_row]
1734 <<
" direction_infinity_norm_ = " << direction_infinity_norm_
1735 <<
" reduced cost = " << reduced_cost;
1736 DCHECK_GE(std::abs(direction_[*leaving_row]),
1737 parameters_.minimum_acceptable_pivot());
1745 const bool is_reduced_cost_positive = (reduced_cost > 0.0);
1746 const bool is_leaving_coeff_positive = (direction_[*leaving_row] > 0.0);
1747 *
target_bound = (is_reduced_cost_positive == is_leaving_coeff_positive)
1748 ? upper_bound_[basis_[*leaving_row]]
1749 : lower_bound_[basis_[*leaving_row]];
1754 ratio_test_stats_.leaving_choices.Add(stats_num_leaving_choices);
1755 if (!equivalent_leaving_choices_.empty()) {
1756 ratio_test_stats_.num_perfect_ties.Add(
1757 equivalent_leaving_choices_.size());
1760 ratio_test_stats_.abs_used_pivot.Add(std::abs(direction_[*leaving_row]));
1782 bool operator<(
const BreakPoint& other)
const {
1783 if (
ratio == other.ratio) {
1785 return row > other.row;
1789 return ratio > other.ratio;
1800 void RevisedSimplex::PrimalPhaseIChooseLeavingVariableRow(
1801 ColIndex entering_col,
Fractional reduced_cost,
bool* refactorize,
1802 RowIndex* leaving_row,
Fractional* step_length,
1809 DCHECK_NE(0.0, reduced_cost);
1813 const Fractional entering_value = variable_values_.
Get(entering_col);
1814 Fractional current_ratio = (reduced_cost > 0.0)
1815 ? entering_value - lower_bound_[entering_col]
1816 : upper_bound_[entering_col] - entering_value;
1817 DCHECK_GT(current_ratio, 0.0);
1819 std::vector<BreakPoint> breakpoints;
1820 const Fractional tolerance = parameters_.primal_feasibility_tolerance();
1821 for (
const auto e : direction_) {
1823 reduced_cost > 0.0 ? e.coefficient() : -e.coefficient();
1824 const Fractional magnitude = std::abs(direction);
1825 if (magnitude < tolerance)
continue;
1840 const ColIndex
col = basis_[e.row()];
1846 const Fractional to_lower = (lower_bound - tolerance -
value) / direction;
1847 const Fractional to_upper = (upper_bound + tolerance -
value) / direction;
1851 if (to_lower >= 0.0 && to_lower < current_ratio) {
1852 breakpoints.push_back(
1853 BreakPoint(e.row(), to_lower, magnitude, lower_bound));
1855 if (to_upper >= 0.0 && to_upper < current_ratio) {
1856 breakpoints.push_back(
1857 BreakPoint(e.row(), to_upper, magnitude, upper_bound));
1863 std::make_heap(breakpoints.begin(), breakpoints.end());
1867 Fractional improvement = std::abs(reduced_cost);
1870 while (!breakpoints.empty()) {
1871 const BreakPoint top = breakpoints.front();
1879 if (top.coeff_magnitude > best_magnitude) {
1880 *leaving_row = top.row;
1881 current_ratio = top.ratio;
1882 best_magnitude = top.coeff_magnitude;
1888 improvement -= top.coeff_magnitude;
1889 if (improvement <= 0.0)
break;
1890 std::pop_heap(breakpoints.begin(), breakpoints.end());
1891 breakpoints.pop_back();
1897 parameters_.small_pivot_threshold() * direction_infinity_norm_;
1898 if (best_magnitude < threshold && !basis_factorization_.
IsRefactorized()) {
1899 *refactorize =
true;
1903 *step_length = current_ratio;
1907 Status RevisedSimplex::DualChooseLeavingVariableRow(RowIndex* leaving_row,
1922 equivalent_leaving_choices_.clear();
1924 const Fractional scaled_best_price = best_price * squared_norm[
row];
1925 if (squared_infeasibilities[
row] >= scaled_best_price) {
1926 if (squared_infeasibilities[
row] == scaled_best_price) {
1928 equivalent_leaving_choices_.push_back(
row);
1931 equivalent_leaving_choices_.clear();
1932 best_price = squared_infeasibilities[
row] / squared_norm[
row];
1938 if (!equivalent_leaving_choices_.empty()) {
1939 equivalent_leaving_choices_.push_back(*leaving_row);
1941 equivalent_leaving_choices_[std::uniform_int_distribution<int>(
1942 0, equivalent_leaving_choices_.size() - 1)(random_)];
1948 const ColIndex leaving_col = basis_[*leaving_row];
1950 if (
value < lower_bound_[leaving_col]) {
1951 *cost_variation = lower_bound_[leaving_col] -
value;
1953 DCHECK_GT(*cost_variation, 0.0);
1955 *cost_variation = upper_bound_[leaving_col] -
value;
1957 DCHECK_LT(*cost_variation, 0.0);
1969 if (
cost == 0.0)
return false;
1970 return type == VariableType::UPPER_AND_LOWER_BOUNDED ||
1972 (type == VariableType::UPPER_BOUNDED &&
cost < -threshold) ||
1973 (type == VariableType::LOWER_BOUNDED &&
cost > threshold);
1978 void RevisedSimplex::DualPhaseIUpdatePrice(RowIndex leaving_row,
1979 ColIndex entering_col) {
1982 const Fractional threshold = parameters_.ratio_test_zero_threshold();
1988 dual_pricing_vector_[leaving_row] / direction_[leaving_row];
1989 for (
const auto e : direction_) {
1990 dual_pricing_vector_[e.row()] -= e.coefficient() * step;
1991 is_dual_entering_candidate_.
Set(
1992 e.row(), IsDualPhaseILeavingCandidate(dual_pricing_vector_[e.row()],
1993 variable_type[basis_[e.row()]],
1996 dual_pricing_vector_[leaving_row] = step;
2000 dual_pricing_vector_[leaving_row] -=
2001 dual_infeasibility_improvement_direction_[entering_col];
2002 if (dual_infeasibility_improvement_direction_[entering_col] != 0.0) {
2003 --num_dual_infeasible_positions_;
2005 dual_infeasibility_improvement_direction_[entering_col] = 0.0;
2008 dual_infeasibility_improvement_direction_[basis_[leaving_row]] = 0.0;
2011 is_dual_entering_candidate_.
Set(
2013 IsDualPhaseILeavingCandidate(dual_pricing_vector_[leaving_row],
2014 variable_type[entering_col], threshold));
2017 template <
typename Cols>
2018 void RevisedSimplex::DualPhaseIUpdatePriceOnReducedCostChange(
2021 bool something_to_do =
false;
2026 for (ColIndex
col : cols) {
2029 (can_increase.IsSet(
col) && reduced_cost < -tolerance) ? 1.0
2030 : (can_decrease.IsSet(
col) && reduced_cost > tolerance) ? -1.0
2032 if (sign != dual_infeasibility_improvement_direction_[
col]) {
2034 --num_dual_infeasible_positions_;
2035 }
else if (dual_infeasibility_improvement_direction_[
col] == 0.0) {
2036 ++num_dual_infeasible_positions_;
2038 if (!something_to_do) {
2039 initially_all_zero_scratchpad_.
values.
resize(num_rows_, 0.0);
2041 initially_all_zero_scratchpad_.
non_zeros.clear();
2042 something_to_do =
true;
2045 col, sign - dual_infeasibility_improvement_direction_[
col],
2046 &initially_all_zero_scratchpad_);
2047 dual_infeasibility_improvement_direction_[
col] = sign;
2050 if (something_to_do) {
2055 const Fractional threshold = parameters_.ratio_test_zero_threshold();
2056 basis_factorization_.
RightSolve(&initially_all_zero_scratchpad_);
2057 if (initially_all_zero_scratchpad_.
non_zeros.empty()) {
2058 for (RowIndex
row(0);
row < num_rows_; ++
row) {
2059 if (initially_all_zero_scratchpad_[
row] == 0.0)
continue;
2060 dual_pricing_vector_[
row] += initially_all_zero_scratchpad_[
row];
2061 is_dual_entering_candidate_.
Set(
2062 row, IsDualPhaseILeavingCandidate(dual_pricing_vector_[
row],
2063 variable_type[basis_[
row]],
2068 for (
const auto e : initially_all_zero_scratchpad_) {
2069 dual_pricing_vector_[e.row()] += e.coefficient();
2070 initially_all_zero_scratchpad_[e.row()] = 0.0;
2071 is_dual_entering_candidate_.
Set(
2072 e.row(), IsDualPhaseILeavingCandidate(
2073 dual_pricing_vector_[e.row()],
2074 variable_type[basis_[e.row()]], threshold));
2077 initially_all_zero_scratchpad_.non_zeros.clear();
2081 Status RevisedSimplex::DualPhaseIChooseLeavingVariableRow(
2082 RowIndex* leaving_row,
Fractional* cost_variation,
2099 dual_pricing_vector_.
empty()) {
2101 num_dual_infeasible_positions_ = 0;
2104 dual_infeasibility_improvement_direction_.
AssignToZero(num_cols_);
2105 DualPhaseIUpdatePriceOnReducedCostChange(
2115 if (num_dual_infeasible_positions_ == 0)
return Status::OK();
2124 equivalent_leaving_choices_.clear();
2125 for (
const RowIndex
row : is_dual_entering_candidate_) {
2127 const Fractional scaled_best_price = best_price * squared_norm[
row];
2128 if (squared_cost >= scaled_best_price) {
2129 if (squared_cost == scaled_best_price) {
2131 equivalent_leaving_choices_.push_back(
row);
2134 equivalent_leaving_choices_.clear();
2135 best_price = squared_cost / squared_norm[
row];
2141 if (!equivalent_leaving_choices_.empty()) {
2142 equivalent_leaving_choices_.push_back(*leaving_row);
2144 equivalent_leaving_choices_[std::uniform_int_distribution<int>(
2145 0, equivalent_leaving_choices_.size() - 1)(random_)];
2151 *cost_variation = dual_pricing_vector_[*leaving_row];
2152 const ColIndex leaving_col = basis_[*leaving_row];
2153 if (*cost_variation < 0.0) {
2162 template <
typename BoxedVariableCols>
2163 void RevisedSimplex::MakeBoxedVariableDualFeasible(
2164 const BoxedVariableCols& cols,
bool update_basic_values) {
2166 std::vector<ColIndex> changed_cols;
2174 const Fractional dual_feasibility_tolerance =
2177 for (
const ColIndex
col : cols) {
2181 VariableType::UPPER_AND_LOWER_BOUNDED);
2183 DCHECK(variable_values[
col] == lower_bound_[
col] ||
2184 variable_values[
col] == upper_bound_[
col] ||
2186 if (reduced_cost > dual_feasibility_tolerance &&
2189 changed_cols.push_back(
col);
2190 }
else if (reduced_cost < -dual_feasibility_tolerance &&
2193 changed_cols.push_back(
col);
2197 if (!changed_cols.empty()) {
2199 update_basic_values);
2203 Fractional RevisedSimplex::ComputeStepToMoveBasicVariableToBound(
2208 const ColIndex leaving_col = basis_[leaving_row];
2209 const Fractional leaving_variable_value = variable_values_.
Get(leaving_col);
2219 return unscaled_step / direction_[leaving_row];
2222 bool RevisedSimplex::TestPivot(ColIndex entering_col, RowIndex leaving_row) {
2223 VLOG(1) <<
"Test pivot.";
2225 const ColIndex leaving_col = basis_[leaving_row];
2226 basis_[leaving_row] = entering_col;
2230 CompactSparseMatrixView basis_matrix(&compact_matrix_, &basis_);
2232 basis_[leaving_row] = leaving_col;
2239 void RevisedSimplex::PermuteBasis() {
2246 if (col_perm.empty())
return;
2252 if (!dual_pricing_vector_.
empty()) {
2269 Status RevisedSimplex::UpdateAndPivot(ColIndex entering_col,
2270 RowIndex leaving_row,
2273 const ColIndex leaving_col = basis_[leaving_row];
2275 lower_bound_[leaving_col] == upper_bound_[leaving_col]
2281 ratio_test_stats_.bound_shift.Add(variable_values_.
Get(leaving_col) -
2284 UpdateBasis(entering_col, leaving_row, leaving_variable_status);
2286 const Fractional pivot_from_direction = direction_[leaving_row];
2290 std::abs(pivot_from_update_row - pivot_from_direction);
2291 if (diff > parameters_.refactorization_threshold() *
2292 (1 + std::abs(pivot_from_direction))) {
2293 VLOG(1) <<
"Refactorizing: imprecise pivot " << pivot_from_direction
2294 <<
" diff = " << diff;
2298 basis_factorization_.
Update(entering_col, leaving_row, direction_));
2306 bool RevisedSimplex::NeedsBasisRefactorization(
bool refactorize) {
2309 const GlopParameters::PricingRule pricing_rule =
2310 feasibility_phase_ ? parameters_.feasibility_rule()
2311 : parameters_.optimization_rule();
2312 if (parameters_.use_dual_simplex()) {
2314 DCHECK_EQ(pricing_rule, GlopParameters::STEEPEST_EDGE);
2317 if (pricing_rule == GlopParameters::STEEPEST_EDGE &&
2325 Status RevisedSimplex::RefactorizeBasisIfNeeded(
bool* refactorize) {
2327 if (NeedsBasisRefactorization(*refactorize)) {
2332 *refactorize =
false;
2351 Status RevisedSimplex::Minimize(TimeLimit*
time_limit) {
2353 Cleanup update_deterministic_time_on_return(
2355 num_consecutive_degenerate_iterations_ = 0;
2356 DisplayIterationInfo();
2357 bool refactorize =
false;
2359 if (feasibility_phase_) {
2375 CorrectErrorsOnVariableValues();
2376 DisplayIterationInfo();
2378 if (feasibility_phase_) {
2390 if (!feasibility_phase_ &&
2391 ComputeObjectiveValue() < primal_objective_limit_) {
2392 VLOG(1) <<
"Stopping the primal simplex because"
2393 <<
" the objective limit " << primal_objective_limit_
2394 <<
" has been reached.";
2395 problem_status_ = ProblemStatus::PRIMAL_FEASIBLE;
2396 objective_limit_reached_ =
true;
2399 }
else if (feasibility_phase_) {
2415 if (feasibility_phase_) {
2418 if (primal_infeasibility <
2419 parameters_.primal_feasibility_tolerance()) {
2420 problem_status_ = ProblemStatus::PRIMAL_FEASIBLE;
2422 VLOG(1) <<
"Infeasible problem! infeasibility = "
2423 << primal_infeasibility;
2424 problem_status_ = ProblemStatus::PRIMAL_INFEASIBLE;
2431 VLOG(1) <<
"Optimal reached, double checking...";
2441 ComputeDirection(entering_col);
2445 entering_col, direction_, &reduced_cost)) {
2446 VLOG(1) <<
"Skipping col #" << entering_col <<
" whose reduced cost is "
2457 if (num_iterations_ == parameters_.max_number_of_iterations() ||
2463 RowIndex leaving_row;
2465 if (feasibility_phase_) {
2466 PrimalPhaseIChooseLeavingVariableRow(entering_col, reduced_cost,
2467 &refactorize, &leaving_row,
2471 ChooseLeavingVariableRow(entering_col, reduced_cost, &refactorize,
2474 if (refactorize)
continue;
2479 VLOG(1) <<
"Infinite step length, double checking...";
2483 if (feasibility_phase_) {
2485 VLOG(1) <<
"Unbounded feasibility problem !?";
2488 VLOG(1) <<
"Unbounded problem.";
2489 problem_status_ = ProblemStatus::PRIMAL_UNBOUNDED;
2491 for (RowIndex
row(0);
row < num_rows_; ++
row) {
2492 const ColIndex
col = basis_[
row];
2493 solution_primal_ray_[
col] = -direction_[
row];
2495 solution_primal_ray_[entering_col] = 1.0;
2503 Fractional step = (reduced_cost > 0.0) ? -step_length : step_length;
2504 if (feasibility_phase_ && leaving_row !=
kInvalidRow) {
2514 step = ComputeStepToMoveBasicVariableToBound(leaving_row,
target_bound);
2518 const ColIndex leaving_col =
2524 bool is_degenerate =
false;
2526 Fractional dir = -direction_[leaving_row] * step;
2534 if (!is_degenerate) {
2535 DCHECK_EQ(step, ComputeStepToMoveBasicVariableToBound(leaving_row,
2543 entering_col, basis_[leaving_row], leaving_row, direction_,
2546 direction_, &update_row_);
2547 if (!is_degenerate) {
2556 UpdateAndPivot(entering_col, leaving_row,
target_bound));
2558 if (is_degenerate) {
2559 timer.AlsoUpdate(&iteration_stats_.degenerate);
2561 timer.AlsoUpdate(&iteration_stats_.normal);
2567 DCHECK_EQ(VariableType::UPPER_AND_LOWER_BOUNDED,
2570 SetNonBasicVariableStatusAndDeriveValue(entering_col,
2572 }
else if (step < 0.0) {
2573 SetNonBasicVariableStatusAndDeriveValue(entering_col,
2580 if (feasibility_phase_ && leaving_row !=
kInvalidRow) {
2584 &objective_[leaving_col]);
2588 if (step_length == 0.0) {
2589 num_consecutive_degenerate_iterations_++;
2591 if (num_consecutive_degenerate_iterations_ > 0) {
2592 iteration_stats_.degenerate_run_size.Add(
2593 num_consecutive_degenerate_iterations_);
2594 num_consecutive_degenerate_iterations_ = 0;
2599 if (num_consecutive_degenerate_iterations_ > 0) {
2600 iteration_stats_.degenerate_run_size.Add(
2601 num_consecutive_degenerate_iterations_);
2616 Status RevisedSimplex::DualMinimize(TimeLimit*
time_limit) {
2617 Cleanup update_deterministic_time_on_return(
2619 num_consecutive_degenerate_iterations_ = 0;
2620 bool refactorize =
false;
2622 bound_flip_candidates_.clear();
2623 pair_to_ignore_.clear();
2626 RowIndex leaving_row;
2631 ColIndex entering_col;
2640 const bool old_refactorize_value = refactorize;
2657 !old_refactorize_value) {
2660 if (dual_residual_error >
2662 VLOG(1) <<
"Recomputing reduced costs. Dual residual = "
2663 << dual_residual_error;
2678 if (!feasibility_phase_) {
2679 MakeBoxedVariableDualFeasible(
2687 if (ComputeObjectiveValue() > dual_objective_limit_) {
2688 VLOG(1) <<
"Stopping the dual simplex because"
2689 <<
" the objective limit " << dual_objective_limit_
2690 <<
" has been reached.";
2691 problem_status_ = ProblemStatus::DUAL_FEASIBLE;
2692 objective_limit_reached_ =
true;
2698 DisplayIterationInfo();
2702 if (!feasibility_phase_) {
2705 MakeBoxedVariableDualFeasible(bound_flip_candidates_,
2707 bound_flip_candidates_.clear();
2712 direction_.non_zeros);
2716 if (feasibility_phase_) {
2725 VLOG(1) <<
"Optimal reached, double checking.";
2729 if (feasibility_phase_) {
2734 if (num_dual_infeasible_positions_ == 0) {
2735 problem_status_ = ProblemStatus::DUAL_FEASIBLE;
2737 problem_status_ = ProblemStatus::DUAL_INFEASIBLE;
2746 for (std::pair<RowIndex, ColIndex> pair : pair_to_ignore_) {
2747 if (pair.first == leaving_row) {
2751 if (feasibility_phase_) {
2753 update_row_, cost_variation, &entering_col, &
ratio));
2756 update_row_, cost_variation, &bound_flip_candidates_, &entering_col,
2763 VLOG(1) <<
"No entering column. Double checking...";
2768 if (feasibility_phase_) {
2770 VLOG(1) <<
"Unbounded dual feasibility problem !?";
2773 problem_status_ = ProblemStatus::DUAL_UNBOUNDED;
2774 solution_dual_ray_ =
2777 solution_dual_ray_row_combination_.
AssignToZero(num_cols_);
2779 solution_dual_ray_row_combination_[
col] =
2782 if (cost_variation < 0) {
2784 ChangeSign(&solution_dual_ray_row_combination_);
2792 if (std::abs(entering_coeff) < parameters_.dual_small_pivot_threshold() &&
2794 VLOG(1) <<
"Trying not to pivot by " << entering_coeff;
2803 ComputeDirection(entering_col);
2804 if (std::abs(direction_[leaving_row]) <
2805 parameters_.minimum_acceptable_pivot()) {
2806 VLOG(1) <<
"Do not pivot by " << entering_coeff
2807 <<
" because the direction is " << direction_[leaving_row];
2809 pair_to_ignore_.push_back({leaving_row, entering_col});
2812 pair_to_ignore_.clear();
2819 if (num_iterations_ == parameters_.max_number_of_iterations() ||
2826 timer.AlsoUpdate(&iteration_stats_.degenerate);
2828 timer.AlsoUpdate(&iteration_stats_.normal);
2840 if (feasibility_phase_) {
2841 DualPhaseIUpdatePrice(leaving_row, entering_col);
2844 ComputeStepToMoveBasicVariableToBound(leaving_row,
target_bound);
2851 entering_col, leaving_row, direction_,
2855 const ColIndex leaving_col = basis_[leaving_row];
2857 UpdateAndPivot(entering_col, leaving_row,
target_bound));
2867 if (std::abs(primal_step) * parameters_.primal_feasibility_tolerance() >
2876 ColIndex RevisedSimplex::SlackColIndex(RowIndex
row)
const {
2884 result.append(iteration_stats_.StatString());
2885 result.append(ratio_test_stats_.StatString());
2886 result.append(entering_variable_.
StatString());
2888 result.append(variable_values_.
StatString());
2889 result.append(primal_edge_norms_.
StatString());
2890 result.append(dual_edge_norms_.
StatString());
2892 result.append(basis_factorization_.
StatString());
2897 void RevisedSimplex::DisplayAllStats() {
2898 if (FLAGS_simplex_display_stats) {
2900 absl::FPrintF(stderr,
"%s", GetPrettySolverStats());
2904 Fractional RevisedSimplex::ComputeObjectiveValue()
const {
2910 Fractional RevisedSimplex::ComputeInitialProblemObjectiveValue()
const {
2914 return objective_scaling_factor_ * (sum + objective_offset_);
2922 PropagateParameters();
2925 void RevisedSimplex::PropagateParameters() {
2935 void RevisedSimplex::DisplayIterationInfo()
const {
2936 if (VLOG_IS_ON(1)) {
2937 const int iter = feasibility_phase_
2939 : num_iterations_ - num_feasibility_iterations_;
2946 ? ComputeInitialProblemObjectiveValue()
2947 : (parameters_.use_dual_simplex()
2948 ? reduced_costs_.ComputeSumOfDualInfeasibilities()
2949 : variable_values_.ComputeSumOfPrimalInfeasibilities());
2950 VLOG(1) << (feasibility_phase_ ?
"Feasibility" :
"Optimization")
2951 <<
" phase, iteration # " << iter
2952 <<
", objective = " << absl::StrFormat(
"%.15E", objective);
2956 void RevisedSimplex::DisplayErrors()
const {
2957 if (VLOG_IS_ON(1)) {
2958 VLOG(1) <<
"Primal infeasibility (bounds) = "
2960 VLOG(1) <<
"Primal residual |A.x - b| = "
2962 VLOG(1) <<
"Dual infeasibility (reduced costs) = "
2964 VLOG(1) <<
"Dual residual |c_B - y.B| = "
2971 std::string StringifyMonomialWithFlags(
const Fractional a,
2972 const std::string& x) {
2978 std::string StringifyWithFlags(
const Fractional x) {
2979 return Stringify(x, FLAGS_simplex_display_numbers_as_fractions);
2984 std::string RevisedSimplex::SimpleVariableInfo(ColIndex
col)
const {
2988 absl::StrAppendFormat(&output,
"%d (%s) = %s, %s, %s, [%s,%s]",
col.value(),
2989 variable_name_[
col],
2990 StringifyWithFlags(variable_values_.
Get(
col)),
2993 StringifyWithFlags(lower_bound_[
col]),
2994 StringifyWithFlags(upper_bound_[
col]));
2998 void RevisedSimplex::DisplayInfoOnVariables()
const {
2999 if (VLOG_IS_ON(3)) {
3000 for (ColIndex
col(0);
col < num_cols_; ++
col) {
3004 objective_coefficient * variable_value;
3005 VLOG(3) << SimpleVariableInfo(
col) <<
". " << variable_name_[
col] <<
" = "
3006 << StringifyWithFlags(variable_value) <<
" * "
3007 << StringifyWithFlags(objective_coefficient)
3008 <<
"(obj) = " << StringifyWithFlags(objective_contribution);
3010 VLOG(3) <<
"------";
3014 void RevisedSimplex::DisplayVariableBounds() {
3015 if (VLOG_IS_ON(3)) {
3017 for (ColIndex
col(0);
col < num_cols_; ++
col) {
3018 switch (variable_type[
col]) {
3019 case VariableType::UNCONSTRAINED:
3021 case VariableType::LOWER_BOUNDED:
3022 VLOG(3) << variable_name_[
col]
3023 <<
" >= " << StringifyWithFlags(lower_bound_[
col]) <<
";";
3025 case VariableType::UPPER_BOUNDED:
3026 VLOG(3) << variable_name_[
col]
3027 <<
" <= " << StringifyWithFlags(upper_bound_[
col]) <<
";";
3029 case VariableType::UPPER_AND_LOWER_BOUNDED:
3030 VLOG(3) << StringifyWithFlags(lower_bound_[
col])
3031 <<
" <= " << variable_name_[
col]
3032 <<
" <= " << StringifyWithFlags(upper_bound_[
col]) <<
";";
3035 VLOG(3) << variable_name_[
col] <<
" = "
3036 << StringifyWithFlags(lower_bound_[
col]) <<
";";
3039 LOG(DFATAL) <<
"Column " <<
col <<
" has no meaningful status.";
3049 for (ColIndex
col(0);
col < num_cols_; ++
col) {
3050 ComputeDirection(
col);
3051 for (
const auto e : direction_) {
3052 if (column_scales ==
nullptr) {
3053 dictionary[e.row()].SetCoefficient(
col, e.coefficient());
3057 col < column_scales->
size() ? (*column_scales)[
col] : 1.0;
3059 ? (*column_scales)[
GetBasis(e.row())]
3061 dictionary[e.row()].SetCoefficient(
3062 col, direction_[e.row()] * (numerator / denominator));
3071 Status status = Initialize(linear_program);
3075 solution_objective_value_ = ComputeInitialProblemObjectiveValue();
3079 void RevisedSimplex::DisplayRevisedSimplexDebugInfo() {
3080 if (VLOG_IS_ON(3)) {
3082 DisplayInfoOnVariables();
3084 std::string output =
"z = " + StringifyWithFlags(ComputeObjectiveValue());
3087 absl::StrAppend(&output, StringifyMonomialWithFlags(reduced_costs[
col],
3088 variable_name_[
col]));
3090 VLOG(3) << output <<
";";
3092 const RevisedSimplexDictionary dictionary(
nullptr,
this);
3094 for (
const SparseRow&
row : dictionary) {
3096 ColIndex basic_col = basis_[r];
3097 absl::StrAppend(&output, variable_name_[basic_col],
" = ",
3098 StringifyWithFlags(variable_values_.
Get(basic_col)));
3099 for (
const SparseRowEntry e :
row) {
3100 if (e.col() != basic_col) {
3101 absl::StrAppend(&output,
3102 StringifyMonomialWithFlags(e.coefficient(),
3103 variable_name_[e.col()]));
3106 VLOG(3) << output <<
";";
3108 VLOG(3) <<
"------";
3109 DisplayVariableBounds();
3114 void RevisedSimplex::DisplayProblem()
const {
3117 if (VLOG_IS_ON(3)) {
3118 DisplayInfoOnVariables();
3119 std::string output =
"min: ";
3120 bool has_objective =
false;
3121 for (ColIndex
col(0);
col < num_cols_; ++
col) {
3123 has_objective |= (coeff != 0.0);
3124 absl::StrAppend(&output,
3125 StringifyMonomialWithFlags(coeff, variable_name_[
col]));
3127 if (!has_objective) {
3128 absl::StrAppend(&output,
" 0");
3130 VLOG(3) << output <<
";";
3131 for (RowIndex
row(0);
row < num_rows_; ++
row) {
3133 for (ColIndex
col(0);
col < num_cols_; ++
col) {
3134 absl::StrAppend(&output,
3135 StringifyMonomialWithFlags(
3137 variable_name_[
col]));
3139 VLOG(3) << output <<
" = 0;";
3141 VLOG(3) <<
"------";
3145 void RevisedSimplex::AdvanceDeterministicTime(TimeLimit*
time_limit) {
3148 const double deterministic_time_delta =
3149 current_deterministic_time - last_deterministic_time_update_;
3150 time_limit->AdvanceDeterministicTime(deterministic_time_delta);
3151 last_deterministic_time_update_ = current_deterministic_time;
3154 #undef DCHECK_COL_BOUNDS
3155 #undef DCHECK_ROW_BOUNDS