Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/validate_wheel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ PYDISTCHECK_ARGS=(
if [[ "${package_dir}" == "python/libcuopt" ]]; then
if [[ "${RAPIDS_CUDA_MAJOR}" == "12" ]]; then
PYDISTCHECK_ARGS+=(
--max-allowed-size-compressed '625Mi'
--max-allowed-size-compressed '635Mi'
)
else
PYDISTCHECK_ARGS+=(
Expand Down
10 changes: 9 additions & 1 deletion cpp/include/cuopt/linear_programming/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,20 @@
#define CUOPT_MIP_ABSOLUTE_TOLERANCE "mip_absolute_tolerance"
#define CUOPT_MIP_RELATIVE_TOLERANCE "mip_relative_tolerance"
#define CUOPT_MIP_INTEGRALITY_TOLERANCE "mip_integrality_tolerance"
#define CUOPT_MIP_BATCH_PDLP_STRONG_BRANCHING "mip_batch_pdlp_strong_branching"
#define CUOPT_MIP_ABSOLUTE_GAP "mip_absolute_gap"
#define CUOPT_MIP_RELATIVE_GAP "mip_relative_gap"
#define CUOPT_MIP_HEURISTICS_ONLY "mip_heuristics_only"
#define CUOPT_MIP_SCALING "mip_scaling"
#define CUOPT_MIP_PRESOLVE "mip_presolve"
#define CUOPT_MIP_CUT_PASSES "mip_cut_passes"
#define CUOPT_MIP_MIXED_INTEGER_ROUNDING_CUTS "mip_mixed_integer_rounding_cuts"
#define CUOPT_MIP_MIXED_INTEGER_GOMORY_CUTS "mip_mixed_integer_gomory_cuts"
#define CUOPT_MIP_KNAPSACK_CUTS "mip_knapsack_cuts"
#define CUOPT_MIP_STRONG_CHVATAL_GOMORY_CUTS "mip_strong_chvatal_gomory_cuts"
#define CUOPT_MIP_REDUCED_COST_STRENGTHENING "mip_reduced_cost_strengthening"
#define CUOPT_MIP_CUT_CHANGE_THRESHOLD "mip_cut_change_threshold"
#define CUOPT_MIP_CUT_MIN_ORTHOGONALITY "mip_cut_min_orthogonality"
#define CUOPT_MIP_BATCH_PDLP_STRONG_BRANCHING "mip_batch_pdlp_strong_branching"
#define CUOPT_SOLUTION_FILE "solution_file"
#define CUOPT_NUM_CPU_THREADS "num_cpu_threads"
#define CUOPT_NUM_GPUS "num_gpus"
Expand Down
18 changes: 14 additions & 4 deletions cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,22 @@ class mip_solver_settings_t {
friend class problem_checking_t;
tolerances_t tolerances;

f_t time_limit = std::numeric_limits<f_t>::infinity();
bool heuristics_only = false;
i_t num_cpu_threads = -1; // -1 means use default number of threads in branch and bound
i_t num_gpus = 1;
f_t time_limit = std::numeric_limits<f_t>::infinity();
i_t node_limit = std::numeric_limits<i_t>::max();
bool heuristics_only = false;
i_t num_cpu_threads = -1; // -1 means use default number of threads in branch and bound
i_t max_cut_passes = 10; // number of cut passes to make
i_t mir_cuts = -1;
i_t mixed_integer_gomory_cuts = -1;
i_t knapsack_cuts = -1;
i_t strong_chvatal_gomory_cuts = -1;
i_t reduced_cost_strengthening = -1;
f_t cut_change_threshold = 1e-3;
f_t cut_min_orthogonality = 0.5;
i_t mip_batch_pdlp_strong_branching = 0;
i_t num_gpus = 1;
bool log_to_console = true;

std::string log_file;
std::string sol_file;
std::string user_problem_file;
Expand Down
1 change: 1 addition & 0 deletions cpp/src/dual_simplex/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ set(DUAL_SIMPLEX_SRC_FILES
${CMAKE_CURRENT_SOURCE_DIR}/basis_updates.cpp
${CMAKE_CURRENT_SOURCE_DIR}/bound_flipping_ratio_test.cpp
${CMAKE_CURRENT_SOURCE_DIR}/branch_and_bound.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cuts.cpp
${CMAKE_CURRENT_SOURCE_DIR}/crossover.cpp
${CMAKE_CURRENT_SOURCE_DIR}/folding.cpp
${CMAKE_CURRENT_SOURCE_DIR}/initial_basis.cpp
Expand Down
10 changes: 5 additions & 5 deletions cpp/src/dual_simplex/barrier.cu
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ class iteration_data_t {
solve_status = chol->solve(U_col, M_col);
if (solve_status != 0) { return solve_status; }
if (settings_.concurrent_halt != nullptr && *settings_.concurrent_halt == 1) {
return -2;
return CONCURRENT_HALT_RETURN;
}
M.set_column(k, M_col);

Expand All @@ -700,7 +700,7 @@ class iteration_data_t {
AD_dense.transpose_multiply(
1.0, M.values.data() + k * M.m, 0.0, H.values.data() + k * H.m);
if (settings_.concurrent_halt != nullptr && *settings_.concurrent_halt == 1) {
return -2;
return CONCURRENT_HALT_RETURN;
}
}

Expand Down Expand Up @@ -1745,7 +1745,7 @@ int barrier_solver_t<i_t, f_t>::initial_point(iteration_data_t<i_t, f_t>& data)
} else {
status = data.chol->factorize(data.device_ADAT);
}
if (status == -2) { return -2; }
if (status == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; }
if (status != 0) {
settings.log.printf("Initial factorization failed\n");
return -1;
Expand Down Expand Up @@ -2309,7 +2309,7 @@ i_t barrier_solver_t<i_t, f_t>::gpu_compute_search_direction(iteration_data_t<i_
data.num_factorizations++;

data.has_solve_info = false;
if (status == -2) { return -2; }
if (status == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; }

if (status < 0) {
settings.log.printf("Factorization failed.\n");
Expand Down Expand Up @@ -2411,7 +2411,7 @@ i_t barrier_solver_t<i_t, f_t>::gpu_compute_search_direction(iteration_data_t<i_
// TODO Chris, we need to write to cpu because dx is used outside
// Can't we also GPUify what's usinng this dx?
raft::copy(dy.data(), data.d_dy_.data(), dy.size(), stream_view_);
if (solve_status == -2) { return -2; }
if (solve_status == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; }
if (solve_status < 0) {
settings.log.printf("Linear solve failed\n");
return -1;
Expand Down
43 changes: 29 additions & 14 deletions cpp/src/dual_simplex/basis_solves.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ i_t factorize_basis(const csc_matrix_t<i_t, f_t>& A,
S_perm_inv);
if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) {
settings.log.printf("Concurrent halt\n");
return -1;
return CONCURRENT_HALT_RETURN;
}
if (Srank != Sdim) {
// Get the rank deficient columns
Expand Down Expand Up @@ -582,7 +582,7 @@ i_t factorize_basis(const csc_matrix_t<i_t, f_t>& A,
}
if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) {
settings.log.printf("Concurrent halt\n");
return -1;
return CONCURRENT_HALT_RETURN;
}
if (verbose) {
printf("Right Lnz+Unz %d t %.3f\n", L.col_start[m] + U.col_start[m], toc(fact_start));
Expand Down Expand Up @@ -619,12 +619,13 @@ i_t basis_repair(const csc_matrix_t<i_t, f_t>& A,
const std::vector<i_t>& slacks_needed,
std::vector<i_t>& basis_list,
std::vector<i_t>& nonbasic_list,
std::vector<i_t>& superbasic_list,
std::vector<variable_status_t>& vstatus)
{
const i_t m = A.m;
const i_t n = A.n;
assert(basis_list.size() == m);
assert(nonbasic_list.size() == n - m);
assert(nonbasic_list.size() + superbasic_list.size() == n - m);

// Create slack_map
std::vector<i_t> slack_map(m); // slack_map[i] = j if column j is e_i
Expand All @@ -649,26 +650,39 @@ i_t basis_repair(const csc_matrix_t<i_t, f_t>& A,
for (i_t k = 0; k < num_nonbasic; ++k) {
nonbasic_map[nonbasic_list[k]] = k;
}
// Create a superbasic_map
std::vector<i_t> superbasic_map(
n, -1); // superbasic_map[j] = p if superbasic[p] = j, -1 if j is basic/nonbasic
const i_t num_superbasic = superbasic_list.size();
for (i_t k = 0; k < num_superbasic; ++k) {
superbasic_map[superbasic_list[k]] = k;
}

const i_t columns_to_replace = deficient.size();
for (i_t k = 0; k < columns_to_replace; ++k) {
const i_t bad_j = basis_list[deficient[k]];
const i_t replace_i = slacks_needed[k];
const i_t replace_j = slack_map[replace_i];
basis_list[deficient[k]] = replace_j;
assert(nonbasic_map[replace_j] != -1);
nonbasic_list[nonbasic_map[replace_j]] = bad_j;
vstatus[replace_j] = variable_status_t::BASIC;
// This is the main issue. What value should bad_j take on.
if (lower[bad_j] == -inf && upper[bad_j] == inf) {
vstatus[bad_j] = variable_status_t::NONBASIC_FREE;
} else if (lower[bad_j] > -inf) {
vstatus[bad_j] = variable_status_t::NONBASIC_LOWER;
} else if (upper[bad_j] < inf) {
vstatus[bad_j] = variable_status_t::NONBASIC_UPPER;
if (nonbasic_map[replace_j] != -1) {
nonbasic_list[nonbasic_map[replace_j]] = bad_j;
// This is the main issue. What value should bad_j take on.
if (lower[bad_j] == -inf && upper[bad_j] == inf) {
vstatus[bad_j] = variable_status_t::NONBASIC_FREE;
} else if (lower[bad_j] > -inf) {
vstatus[bad_j] = variable_status_t::NONBASIC_LOWER;
} else if (upper[bad_j] < inf) {
vstatus[bad_j] = variable_status_t::NONBASIC_UPPER;
} else {
assert(1 == 0);
}
} else if (superbasic_map[replace_j] != -1) {
superbasic_list[superbasic_map[replace_j]] = bad_j;
vstatus[bad_j] = variable_status_t::SUPERBASIC;
} else {
assert(1 == 0);
assert(nonbasic_map[replace_j] != -1 || superbasic_map[replace_j] != -1);
}
Comment on lines +667 to 684
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check the variable_status_t enum to see if NONBASIC_FIXED exists
rg -n 'variable_status_t|NONBASIC_FIXED' --type=cpp --type=hpp -A2 -B2 | head -80

Repository: NVIDIA/cuopt

Length of output: 83


🏁 Script executed:

# Examine the basis_solves.cpp file structure around lines 667-684
sed -n '650,700p' cpp/src/dual_simplex/basis_solves.cpp

Repository: NVIDIA/cuopt

Length of output: 1947


🏁 Script executed:

# Check how nonbasic_map and superbasic_map are used after line 684 in this function
rg -n 'nonbasic_map|superbasic_map' cpp/src/dual_simplex/basis_solves.cpp | tail -20

Repository: NVIDIA/cuopt

Length of output: 766


🏁 Script executed:

# Find the variable_status_t enum definition
rg -n 'enum.*variable_status_t|NONBASIC_FIXED' -t c -t h -C3

Repository: NVIDIA/cuopt

Length of output: 789


🏁 Script executed:

# Check the function signature and full context
sed -n '620,690p' cpp/src/dual_simplex/basis_solves.cpp

Repository: NVIDIA/cuopt

Length of output: 2643


🏁 Script executed:

# Find where this function is defined and used
rg -n 'fix_basis|deficient_basis' cpp/src/dual_simplex/basis_solves.cpp -A1 -B1 | head -30

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

# Check how NONBASIC_FIXED is used in the codebase
rg -n 'NONBASIC_FIXED' --type-list

Repository: NVIDIA/cuopt

Length of output: 445


🏁 Script executed:

# Look for uses of NONBASIC_FIXED to understand if it's important for correctness
rg -n 'NONBASIC_FIXED' -C2

Repository: NVIDIA/cuopt

Length of output: 11626


🏁 Script executed:

# Check if the bounds checking logic elsewhere handles NONBASIC_FIXED
rg -n 'variable_status_t::NONBASIC' -C2 | head -100

Repository: NVIDIA/cuopt

Length of output: 7606


Missing NONBASIC_FIXED status assignment for fixed variables (lower == upper).

The bound-checking logic (lines 670–678) omits the case where lower[bad_j] == upper[bad_j]. This matters: elsewhere in the dual simplex solver (primal.cpp:465, phase2.cpp:1999, crossover.cpp:460 and beyond), variables with equal bounds are explicitly marked NONBASIC_FIXED and handled differently from NONBASIC_LOWER or NONBASIC_UPPER. The current code assigns NONBASIC_LOWER to such variables, which is corrected later but indicates an initial basis inconsistency.

Add a check before the lower > -inf condition:

if (lower[bad_j] == -inf && upper[bad_j] == inf) {
  vstatus[bad_j] = variable_status_t::NONBASIC_FREE;
} else if (lower[bad_j] > -inf && lower[bad_j] == upper[bad_j]) {
  vstatus[bad_j] = variable_status_t::NONBASIC_FIXED;
} else if (lower[bad_j] > -inf) {
  vstatus[bad_j] = variable_status_t::NONBASIC_LOWER;
} else if (upper[bad_j] < inf) {
  vstatus[bad_j] = variable_status_t::NONBASIC_UPPER;
} else {
  assert(false && "unexpected bound configuration");
}

Also prefer assert(false) with a descriptive message over assert(1 == 0) (lines 677, 683).

🤖 Prompt for AI Agents
In `@cpp/src/dual_simplex/basis_solves.cpp` around lines 667 - 684, The branch
that assigns vstatus for bad_j (inside the if checking nonbasic_map[replace_j])
misses the case where lower[bad_j] == upper[bad_j] (fixed variable) and
currently falls through to NONBASIC_LOWER; update the condition order in that
block to check for fixed bounds first and assign
variable_status_t::NONBASIC_FIXED when lower[bad_j] == upper[bad_j], keeping the
existing checks for NONBASIC_FREE, NONBASIC_LOWER and NONBASIC_UPPER for the
other cases; also replace the final assert(1 == 0) with assert(false &&
"unexpected bound configuration") to provide a clearer failure message.

vstatus[replace_j] = variable_status_t::BASIC;
}

return 0;
Expand Down Expand Up @@ -865,6 +879,7 @@ template int basis_repair<int, double>(const csc_matrix_t<int, double>& A,
const std::vector<int>& slacks_needed,
std::vector<int>& basis_list,
std::vector<int>& nonbasic_list,
std::vector<int>& superbasic_list,
std::vector<variable_status_t>& vstatus);

template int form_b<int, double>(const csc_matrix_t<int, double>& A,
Expand Down
1 change: 1 addition & 0 deletions cpp/src/dual_simplex/basis_solves.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ i_t basis_repair(const csc_matrix_t<i_t, f_t>& A,
const std::vector<i_t>& slacks_needed,
std::vector<i_t>& basis_list,
std::vector<i_t>& nonbasic_list,
std::vector<i_t>& superbasic_list,
std::vector<variable_status_t>& vstatus);

// Form the basis matrix B = A(:, basic_list)
Expand Down
Loading