Skip to content

Commit 3204e5a

Browse files
authored
Fix bug in scaling of dual slacks and sign of dual variables for >= constraints (#191)
Fixes a bug in how dual slacks (reduced costs) were unscaled. Fixes #183 : dual simplex produces the wrong sign for dual variables with >= constraints ## Issue closes #183 Authors: - Chris Maes (https://github.com/chris-maes) Approvers: - Rajesh Gandham (https://github.com/rg20) URL: #191
1 parent e5dd284 commit 3204e5a

5 files changed

Lines changed: 112 additions & 3 deletions

File tree

cpp/src/dual_simplex/presolve.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,30 @@ void uncrush_primal_solution(const user_problem_t<i_t, f_t>& user_problem,
842842
std::copy(solution.begin(), solution.begin() + user_problem.num_cols, user_solution.data());
843843
}
844844

845+
template <typename i_t, typename f_t>
846+
void uncrush_dual_solution(const user_problem_t<i_t, f_t>& user_problem,
847+
const lp_problem_t<i_t, f_t>& problem,
848+
const std::vector<f_t>& y,
849+
const std::vector<f_t>& z,
850+
std::vector<f_t>& user_y,
851+
std::vector<f_t>& user_z)
852+
{
853+
// Reduced costs are uncrushed just like the primal solution
854+
uncrush_primal_solution(user_problem, problem, z, user_z);
855+
856+
// Adjust the sign of the dual variables y
857+
// We should have A^T y + z = c
858+
// In convert_user_problem, we converted >= to <=, so we need to adjust the sign of the dual
859+
// variables
860+
for (i_t i = 0; i < user_problem.num_rows; i++) {
861+
if (user_problem.row_sense[i] == 'G') {
862+
user_y[i] = -y[i];
863+
} else {
864+
user_y[i] = y[i];
865+
}
866+
}
867+
}
868+
845869
template <typename i_t, typename f_t>
846870
void uncrush_solution(const presolve_info_t<i_t, f_t>& presolve_info,
847871
const std::vector<f_t>& crushed_x,
@@ -903,6 +927,13 @@ template void uncrush_primal_solution<int, double>(const user_problem_t<int, dou
903927
const std::vector<double>& solution,
904928
std::vector<double>& user_solution);
905929

930+
template void uncrush_dual_solution<int, double>(const user_problem_t<int, double>& user_problem,
931+
const lp_problem_t<int, double>& problem,
932+
const std::vector<double>& y,
933+
const std::vector<double>& z,
934+
std::vector<double>& user_y,
935+
std::vector<double>& user_z);
936+
906937
template void uncrush_solution<int, double>(const presolve_info_t<int, double>& presolve_info,
907938
const std::vector<double>& crushed_x,
908939
const std::vector<double>& crushed_z,

cpp/src/dual_simplex/presolve.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ void uncrush_primal_solution(const user_problem_t<i_t, f_t>& user_problem,
115115
const std::vector<f_t>& solution,
116116
std::vector<f_t>& user_solution);
117117

118+
template <typename i_t, typename f_t>
119+
void uncrush_dual_solution(const user_problem_t<i_t, f_t>& user_problem,
120+
const lp_problem_t<i_t, f_t>& problem,
121+
const std::vector<f_t>& y,
122+
const std::vector<f_t>& z,
123+
std::vector<f_t>& user_y,
124+
std::vector<f_t>& user_z);
125+
118126
template <typename i_t, typename f_t>
119127
void uncrush_solution(const presolve_info_t<i_t, f_t>& presolve_info,
120128
const std::vector<f_t>& crushed_x,

cpp/src/dual_simplex/scaling.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ void unscale_solution(const std::vector<f_t>& column_scaling,
8888
unscaled_z.resize(n);
8989
for (i_t j = 0; j < n; ++j) {
9090
unscaled_x[j] = scaled_x[j] / column_scaling[j];
91-
unscaled_z[j] = scaled_z[j] / column_scaling[j];
91+
unscaled_z[j] = scaled_z[j] * column_scaling[j];
9292
}
9393
}
9494

cpp/src/dual_simplex/solve.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,8 @@ lp_status_t solve_linear_program(const user_problem_t<i_t, f_t>& user_problem,
252252
lp_status_t status = solve_linear_program_advanced(
253253
original_lp, start_time, settings, lp_solution, vstatus, edge_norms);
254254
uncrush_primal_solution(user_problem, original_lp, lp_solution.x, solution.x);
255-
uncrush_primal_solution(user_problem, original_lp, lp_solution.z, solution.z);
256-
solution.y = lp_solution.y;
255+
uncrush_dual_solution(
256+
user_problem, original_lp, lp_solution.y, lp_solution.z, solution.y, solution.z);
257257
solution.objective = lp_solution.objective;
258258
solution.user_objective = lp_solution.user_objective;
259259
solution.iterations = lp_solution.iterations;

cpp/tests/dual_simplex/unit_tests/solve.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,4 +262,74 @@ TEST(dual_simplex, empty_columns)
262262
EXPECT_NEAR(solution.x[8], 0, 1e-6);
263263
}
264264

265+
TEST(dual_simplex, dual_variable_greater_than)
266+
{
267+
// minimize 3*x0 + 2 * x1
268+
// subject to x0 + x1 >= 1
269+
// x0 + 2x1 >= 3
270+
// x0, x1 >= 0
271+
272+
cuopt::linear_programming::dual_simplex::user_problem_t<int, double> user_problem;
273+
constexpr int m = 2;
274+
constexpr int n = 2;
275+
constexpr int nz = 4;
276+
277+
user_problem.num_rows = m;
278+
user_problem.num_cols = n;
279+
user_problem.objective.resize(n);
280+
user_problem.objective[0] = 3.0;
281+
user_problem.objective[1] = 2.0;
282+
user_problem.A.m = m;
283+
user_problem.A.n = n;
284+
user_problem.A.nz_max = nz;
285+
user_problem.A.reallocate(nz);
286+
user_problem.A.col_start.resize(n + 1);
287+
user_problem.A.col_start[0] = 0; // x0 start
288+
user_problem.A.col_start[1] = 2;
289+
user_problem.A.col_start[2] = 4;
290+
291+
int nnz = 0;
292+
user_problem.A.i[nnz] = 0;
293+
user_problem.A.x[nnz++] = 1.0;
294+
user_problem.A.i[nnz] = 1;
295+
user_problem.A.x[nnz++] = 1.0;
296+
user_problem.A.i[nnz] = 0;
297+
user_problem.A.x[nnz++] = 1.0;
298+
user_problem.A.i[nnz] = 1;
299+
user_problem.A.x[nnz++] = 2.0;
300+
user_problem.A.print_matrix();
301+
EXPECT_EQ(nnz, nz);
302+
303+
user_problem.rhs.resize(m);
304+
user_problem.rhs[0] = 1.0;
305+
user_problem.rhs[1] = 3.0;
306+
307+
user_problem.row_sense.resize(m);
308+
user_problem.row_sense[0] = 'G';
309+
user_problem.row_sense[1] = 'G';
310+
311+
user_problem.lower.resize(n);
312+
user_problem.lower[0] = 0.0;
313+
user_problem.lower[1] = 0.0;
314+
315+
user_problem.upper.resize(n);
316+
user_problem.upper[0] = dual_simplex::inf;
317+
user_problem.upper[1] = dual_simplex::inf;
318+
319+
user_problem.num_range_rows = 0;
320+
user_problem.problem_name = "dual_variable_greater_than";
321+
322+
dual_simplex::simplex_solver_settings_t<int, double> settings;
323+
dual_simplex::lp_solution_t<int, double> solution(user_problem.num_rows, user_problem.num_cols);
324+
EXPECT_EQ((dual_simplex::solve_linear_program(user_problem, settings, solution)),
325+
dual_simplex::lp_status_t::OPTIMAL);
326+
EXPECT_NEAR(solution.objective, 3.0, 1e-6);
327+
EXPECT_NEAR(solution.x[0], 0.0, 1e-6);
328+
EXPECT_NEAR(solution.x[1], 1.5, 1e-6);
329+
EXPECT_NEAR(solution.y[0], 0.0, 1e-6);
330+
EXPECT_NEAR(solution.y[1], 1.0, 1e-6);
331+
EXPECT_NEAR(solution.z[0], 2.0, 1e-6);
332+
EXPECT_NEAR(solution.z[1], 0.0, 1e-6);
333+
}
334+
265335
} // namespace cuopt::linear_programming::dual_simplex::test

0 commit comments

Comments
 (0)