Skip to content

Commit ebda755

Browse files
authored
Fix a bug in basis repair. Recover from numerical issues in primal update. (#249)
This PR includes two fixes: * Basis repair was failing due to a bug in the right-looking LU where the column permutation was not fully populated when the basis was rank deficient. * When computing delta x using a hypersparse solve the solution vector sometimes did not contain a nonzero coefficient associated with the basic leaving index. This was an issue, as we divide by that coefficient to compute the primal step length. This coefficient may have been missing due to the numerical drop tolerances. To recover from this we perform a regular solve. If the coefficient is small, but nonzero, we can recover. In addition this PR adds the option to use threshold rook pivoting inside the right-looking LU factorization. TRP is rank-revealing. However, it produces factors L and U with more fill and is thus about 3% slower. We add TRP in this PR, but keep it disabled behind a flag. A future PR may want to use TRP when basis repair fails. New or existing tests cover these changes. The documentation is not affected. Authors: - Chris Maes (https://github.com/chris-maes) Approvers: - Akif ÇÖRDÜK (https://github.com/akifcorduk) URL: #249
1 parent a807755 commit ebda755

File tree

3 files changed

+165
-19
lines changed

3 files changed

+165
-19
lines changed

cpp/src/dual_simplex/basis_solves.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ i_t factorize_basis(const csc_matrix_t<i_t, f_t>& A,
368368
S, settings.threshold_partial_pivoting_tol, identity, S_col_perm, SL, SU, S_perm_inv);
369369
if (Srank != Sdim) {
370370
// Get the rank deficient columns
371+
deficient.clear();
371372
deficient.resize(Sdim - Srank);
372373
for (i_t h = Srank; h < Sdim; ++h) {
373374
deficient[h - Srank] = col_perm[num_singletons + S_col_perm[h]];

cpp/src/dual_simplex/phase2.cpp

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,8 +1604,20 @@ i_t compute_delta_x(const lp_problem_t<i_t, f_t>& lp,
16041604
f_t scale = scaled_delta_xB_sparse.find_coefficient(basic_leaving_index);
16051605
if (scale != scale) {
16061606
// We couldn't find a coefficient for the basic leaving index.
1607-
// Either this is a bug or the primal step length is inf.
1608-
return -1;
1607+
// The coefficient might be very small. Switch to a regular solve and try to recover.
1608+
std::vector<f_t> rhs;
1609+
rhs_sparse.to_dense(rhs);
1610+
const i_t m = basic_list.size();
1611+
std::vector<f_t> scaled_delta_xB(m);
1612+
ft.b_solve(rhs, scaled_delta_xB);
1613+
if (scaled_delta_xB[basic_leaving_index] != 0.0 &&
1614+
!std::isnan(scaled_delta_xB[basic_leaving_index])) {
1615+
scaled_delta_xB_sparse.from_dense(scaled_delta_xB);
1616+
scaled_delta_xB_sparse.negate();
1617+
scale = -scaled_delta_xB[basic_leaving_index];
1618+
} else {
1619+
return -1;
1620+
}
16091621
}
16101622
const f_t primal_step_length = delta_x_leaving / scale;
16111623
const i_t scaled_delta_xB_nz = scaled_delta_xB_sparse.i.size();
@@ -2856,26 +2868,56 @@ dual::status_t dual_phase2(i_t phase,
28562868
phase2::check_basic_infeasibilities(basic_list, basic_mark, infeasibility_indices, 6);
28572869
#endif
28582870
if (should_refactor) {
2871+
bool should_recompute_x = false;
28592872
if (factorize_basis(lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed) ==
28602873
-1) {
2874+
should_recompute_x = true;
2875+
settings.log.printf("Failed to factorize basis. Iteration %d\n", iter);
28612876
if (toc(start_time) > settings.time_limit) { return dual::status_t::TIME_LIMIT; }
28622877
basis_repair(lp.A, settings, deficient, slacks_needed, basic_list, nonbasic_list, vstatus);
28632878
i_t count = 0;
28642879
while (factorize_basis(
28652880
lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed) == -1) {
2866-
settings.log.printf("Failed to repair basis. %d deficient columns.\n",
2881+
settings.log.printf("Failed to repair basis. Iteration %d. %d deficient columns.\n",
2882+
iter,
28672883
static_cast<int>(deficient.size()));
28682884
if (toc(start_time) > settings.time_limit) { return dual::status_t::TIME_LIMIT; }
28692885
settings.threshold_partial_pivoting_tol = 1.0;
28702886
count++;
2871-
if (count > 100) { return dual::status_t::NUMERICAL; }
2887+
if (count > 10) { return dual::status_t::NUMERICAL; }
28722888
basis_repair(
28732889
lp.A, settings, deficient, slacks_needed, basic_list, nonbasic_list, vstatus);
2890+
2891+
#ifdef CHECK_BASIS_REPAIR
2892+
csc_matrix_t<i_t, f_t> B(m, m, 0);
2893+
form_b(lp.A, basic_list, B);
2894+
for (i_t k = 0; k < deficient.size(); ++k) {
2895+
const i_t j = deficient[k];
2896+
const i_t col_start = B.col_start[j];
2897+
const i_t col_end = B.col_start[j + 1];
2898+
const i_t col_nz = col_end - col_start;
2899+
if (col_nz != 1) {
2900+
settings.log.printf("Deficient column %d has %d nonzeros\n", j, col_nz);
2901+
}
2902+
const i_t i = B.i[col_start];
2903+
if (i != slacks_needed[k]) {
2904+
settings.log.printf("Slack %d needed but found %d instead\n", slacks_needed[k], i);
2905+
}
2906+
}
2907+
#endif
28742908
}
2909+
2910+
settings.log.printf("Successfully repaired basis. Iteration %d\n", iter);
28752911
}
28762912
reorder_basic_list(q, basic_list);
28772913
ft.reset(L, U, p);
28782914
phase2::reset_basis_mark(basic_list, nonbasic_list, basic_mark, nonbasic_mark);
2915+
if (should_recompute_x) {
2916+
std::vector<f_t> unperturbed_x(n);
2917+
phase2::compute_primal_solution_from_basis(
2918+
lp, ft, basic_list, nonbasic_list, vstatus, unperturbed_x);
2919+
x = unperturbed_x;
2920+
}
28792921
phase2::compute_initial_primal_infeasibilities(
28802922
lp, settings, basic_list, x, squared_infeasibilities, infeasibility_indices);
28812923
}

0 commit comments

Comments
 (0)