Skip to content

Commit 721a56a

Browse files
committed
Fix bug where batch PDLP for strong branching was running on problem without cuts
1 parent d862480 commit 721a56a

File tree

3 files changed

+88
-58
lines changed

3 files changed

+88
-58
lines changed

cpp/src/branch_and_bound/branch_and_bound.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2407,10 +2407,10 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
24072407
pc_.resize(original_lp_.num_cols);
24082408
{
24092409
raft::common::nvtx::range scope_sb("BB::strong_branching");
2410-
strong_branching<i_t, f_t>(original_problem_,
2411-
original_lp_,
2410+
strong_branching<i_t, f_t>(original_lp_,
24122411
settings_,
24132412
exploration_stats_.start_time,
2413+
new_slacks_,
24142414
var_types_,
24152415
root_relax_soln_.x,
24162416
fractional,

cpp/src/branch_and_bound/pseudo_costs.cpp

Lines changed: 84 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -220,15 +220,46 @@ f_t trial_branching(const lp_problem_t<i_t, f_t>& original_lp,
220220

221221
template <typename i_t, typename f_t>
222222
static cuopt::mps_parser::mps_data_model_t<i_t, f_t> simplex_problem_to_mps_data_model(
223-
const dual_simplex::user_problem_t<i_t, f_t>& user_problem)
223+
const dual_simplex::lp_problem_t<i_t, f_t>& lp,
224+
const std::vector<i_t>& new_slacks,
225+
const std::vector<f_t>& root_soln,
226+
std::vector<f_t>& original_root_soln_x)
224227
{
228+
229+
// Branch and bound has a problem of the form:
230+
// minimize c^T x
231+
// subject to A*x + Es = b
232+
// l <= x <= u
233+
// E_{jj} = sigma_j, where sigma_j is +1 or -1
234+
235+
// We need to convert this into a problem that is better for PDLP
236+
// to solve. PDLP perfers inequality constraints. Thus, we want
237+
// to convert the above into the problem:
238+
// minimize c^T x
239+
// subject to lb <= A*x <= ub
240+
// l <= x <= u
241+
242+
225243
cuopt::mps_parser::mps_data_model_t<i_t, f_t> mps_model;
226-
int m = user_problem.num_rows;
227-
int n = user_problem.num_cols;
244+
int m = lp.num_rows;
245+
int n = lp.num_cols - new_slacks.size();
246+
original_root_soln_x.resize(n);
247+
248+
// Remove slacks from A
249+
dual_simplex::csc_matrix_t<i_t, f_t> A_no_slacks = lp.A;
250+
std::vector<i_t> cols_to_remove(lp.A.n, 0);
251+
for (i_t j : new_slacks) {
252+
cols_to_remove[j] = 1;
253+
}
254+
A_no_slacks.remove_columns(cols_to_remove);
255+
256+
for (i_t j = 0; j < n; j++) {
257+
original_root_soln_x[j] = root_soln[j];
258+
}
228259

229260
// Convert CSC to CSR using built-in method
230261
dual_simplex::csr_matrix_t<i_t, f_t> csr_A(m, n, 0);
231-
user_problem.A.to_compressed_row(csr_A);
262+
A_no_slacks.to_compressed_row(csr_A);
232263

233264
int nz = csr_A.row_start[m];
234265

@@ -237,70 +268,74 @@ static cuopt::mps_parser::mps_data_model_t<i_t, f_t> simplex_problem_to_mps_data
237268
csr_A.x.data(), nz, csr_A.j.data(), nz, csr_A.row_start.data(), m + 1);
238269

239270
// Set objective coefficients
240-
mps_model.set_objective_coefficients(user_problem.objective.data(), n);
271+
mps_model.set_objective_coefficients(lp.objective.data(), n);
241272

242273
// Set objective scaling and offset
243-
mps_model.set_objective_scaling_factor(user_problem.obj_scale);
244-
mps_model.set_objective_offset(user_problem.obj_constant);
274+
mps_model.set_objective_scaling_factor(lp.obj_scale);
275+
mps_model.set_objective_offset(lp.obj_constant);
245276

246277
// Set variable bounds
247-
mps_model.set_variable_lower_bounds(user_problem.lower.data(), n);
248-
mps_model.set_variable_upper_bounds(user_problem.upper.data(), n);
278+
mps_model.set_variable_lower_bounds(lp.lower.data(), n);
279+
mps_model.set_variable_upper_bounds(lp.upper.data(), n);
249280

250281
// Convert row sense and RHS to constraint bounds
251282
std::vector<f_t> constraint_lower(m);
252283
std::vector<f_t> constraint_upper(m);
253284

254-
for (i_t i = 0; i < m; ++i) {
255-
if (user_problem.row_sense[i] == 'L') {
256-
constraint_lower[i] = -std::numeric_limits<f_t>::infinity();
257-
constraint_upper[i] = user_problem.rhs[i];
258-
} else if (user_problem.row_sense[i] == 'G') {
259-
constraint_lower[i] = user_problem.rhs[i];
260-
constraint_upper[i] = std::numeric_limits<f_t>::infinity();
261-
} else {
262-
constraint_lower[i] = user_problem.rhs[i];
263-
constraint_upper[i] = user_problem.rhs[i];
264-
}
285+
std::vector<i_t> slack_map(m, -1);
286+
for (i_t j : new_slacks) {
287+
const i_t col_start = lp.A.col_start[j];
288+
const i_t i = lp.A.i[col_start];
289+
slack_map[i] = j;
265290
}
266291

267-
for (i_t k = 0; k < user_problem.num_range_rows; ++k) {
268-
i_t i = user_problem.range_rows[k];
269-
f_t r = user_problem.range_value[k];
270-
f_t b = user_problem.rhs[i];
271-
f_t h = -std::numeric_limits<f_t>::infinity();
272-
f_t u = std::numeric_limits<f_t>::infinity();
273-
if (user_problem.row_sense[i] == 'L') {
274-
h = b - std::abs(r);
275-
u = b;
276-
} else if (user_problem.row_sense[i] == 'G') {
277-
h = b;
278-
u = b + std::abs(r);
279-
} else if (user_problem.row_sense[i] == 'E') {
280-
if (r > 0) {
281-
h = b;
282-
u = b + std::abs(r);
283-
} else {
284-
h = b - std::abs(r);
285-
u = b;
286-
}
292+
for (i_t i = 0; i < m; ++i) {
293+
// Each row is of the form a_i^T x + sigma * s_i = b_i
294+
// with sigma = +1 or -1
295+
// and l_i <= s_i <= u_i
296+
// We have that a_i^T x - b_i = -sigma * s_i
297+
// If sigma = -1, then we have
298+
// a_i^T x - b_i = s_i
299+
// l_i <= a_i^T x - b_i <= u_i
300+
// l_i + b_i <= a_i^T x <= u_i + b_i
301+
//
302+
// If sigma = +1, then we have
303+
// a_i^T x - b_i = -s_i
304+
// -a_i^T x + b_i = s_i
305+
// l_i <= -a_i^T x + b_i <= u_i
306+
// l_i - b_i <= -a_i^T x <= u_i - b_i
307+
// -u_i + b_i <= a_i^T x <= -l_i + b_i
308+
309+
const i_t slack = slack_map[i];
310+
assert(slack != -1);
311+
const i_t col_start = lp.A.col_start[slack];
312+
const f_t sigma = lp.A.x[col_start];
313+
const f_t slack_lower = lp.lower[slack];
314+
const f_t slack_upper = lp.upper[slack];
315+
316+
if (sigma == -1) {
317+
constraint_lower[i] = slack_lower + lp.rhs[i];
318+
constraint_upper[i] = slack_upper + lp.rhs[i];
319+
} else if (sigma == 1) {
320+
constraint_lower[i] = -slack_upper + lp.rhs[i];
321+
constraint_upper[i] = -slack_lower + lp.rhs[i];
322+
} else {
323+
assert(sigma == 1.0 || sigma == -1.0);
287324
}
288-
constraint_lower[i] = h;
289-
constraint_upper[i] = u;
290325
}
291326

292327
mps_model.set_constraint_lower_bounds(constraint_lower.data(), m);
293328
mps_model.set_constraint_upper_bounds(constraint_upper.data(), m);
294-
mps_model.set_maximize(user_problem.obj_scale < 0);
329+
mps_model.set_maximize(lp.obj_scale < 0);
295330

296331
return mps_model;
297332
}
298333

299334
template <typename i_t, typename f_t>
300-
void strong_branching(const user_problem_t<i_t, f_t>& original_problem,
301-
const lp_problem_t<i_t, f_t>& original_lp,
335+
void strong_branching(const lp_problem_t<i_t, f_t>& original_lp,
302336
const simplex_solver_settings_t<i_t, f_t>& settings,
303337
f_t start_time,
338+
const std::vector<i_t>& new_slacks,
304339
const std::vector<variable_type_t>& var_types,
305340
const std::vector<f_t> root_soln,
306341
const std::vector<i_t>& fractional,
@@ -321,14 +356,10 @@ void strong_branching(const user_problem_t<i_t, f_t>& original_problem,
321356
settings.log.printf("Batch PDLP strong branching enabled\n");
322357

323358
f_t start_batch = tic();
359+
std::vector<f_t> original_root_soln_x;
324360

325-
// Use original_problem to create the BatchLP problem
326-
csr_matrix_t<i_t, f_t> A_row(original_problem.A.m, original_problem.A.n, 0);
327-
original_problem.A.to_compressed_row(A_row);
361+
const auto mps_model = simplex_problem_to_mps_data_model(original_lp, new_slacks, root_soln, original_root_soln_x);
328362

329-
// Convert the root_soln to the original problem space
330-
std::vector<f_t> original_root_soln_x;
331-
uncrush_primal_solution(original_problem, original_lp, root_soln, original_root_soln_x);
332363

333364
std::vector<f_t> fraction_values;
334365

@@ -337,7 +368,6 @@ void strong_branching(const user_problem_t<i_t, f_t>& original_problem,
337368
fraction_values.push_back(original_root_soln_x[j]);
338369
}
339370

340-
const auto mps_model = simplex_problem_to_mps_data_model(original_problem);
341371
const f_t batch_elapsed_time = toc(start_time);
342372
const f_t batch_remaining_time =
343373
std::max(static_cast<f_t>(0.0), settings.time_limit - batch_elapsed_time);
@@ -776,10 +806,10 @@ void pseudo_costs_t<i_t, f_t>::update_pseudo_costs_from_strong_branching(
776806

777807
template class pseudo_costs_t<int, double>;
778808

779-
template void strong_branching<int, double>(const user_problem_t<int, double>& original_problem,
780-
const lp_problem_t<int, double>& original_lp,
809+
template void strong_branching<int, double>(const lp_problem_t<int, double>& original_lp,
781810
const simplex_solver_settings_t<int, double>& settings,
782811
double start_time,
812+
const std::vector<int>& new_slacks,
783813
const std::vector<variable_type_t>& var_types,
784814
const std::vector<double> root_soln,
785815
const std::vector<int>& fractional,

cpp/src/branch_and_bound/pseudo_costs.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -517,10 +517,10 @@ class pseudo_costs_t {
517517
};
518518

519519
template <typename i_t, typename f_t>
520-
void strong_branching(const user_problem_t<i_t, f_t>& original_problem,
521-
const lp_problem_t<i_t, f_t>& original_lp,
520+
void strong_branching(const lp_problem_t<i_t, f_t>& original_lp,
522521
const simplex_solver_settings_t<i_t, f_t>& settings,
523522
f_t start_time,
523+
const std::vector<i_t>& new_slacks,
524524
const std::vector<variable_type_t>& var_types,
525525
const std::vector<f_t> root_soln,
526526
const std::vector<i_t>& fractional,

0 commit comments

Comments
 (0)