Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
594ec3f
add initial files
akifcorduk Oct 31, 2025
f10535f
add some comments and file name changes
akifcorduk Nov 4, 2025
649062c
initial data structures
akifcorduk Nov 10, 2025
fe4cc7a
find all initial cliques
akifcorduk Nov 12, 2025
1d46ec9
remove small cliques
akifcorduk Nov 13, 2025
1474bc5
renumber cliques on addlt
akifcorduk Nov 13, 2025
b82f63f
clique extension is working
akifcorduk Nov 17, 2025
1289cbc
Merge branch 'main' of github.com:NVIDIA/cuopt into cliques
akifcorduk Nov 17, 2025
60edd8e
add extended cliques into formulation
akifcorduk Nov 18, 2025
103b4c2
find constraints to remove
akifcorduk Nov 18, 2025
adc9c73
wip
akifcorduk Nov 19, 2025
5934c12
Merge branch 'main' of github.com:NVIDIA/cuopt into cliques
akifcorduk Jan 16, 2026
43f2ed0
Merge branch 'main' of github.com:NVIDIA/cuopt into cliques
akifcorduk Jan 23, 2026
3001b52
habdle range constraints
akifcorduk Jan 23, 2026
b45c27e
fix bugs and covert to gpu problem
akifcorduk Jan 26, 2026
21e565a
fix a log
akifcorduk Jan 26, 2026
3ae6047
move preprocessing to presolve
akifcorduk Jan 27, 2026
2c78d02
fix issues and handle ai reviews
akifcorduk Jan 30, 2026
8409f17
fix bugs adj list
akifcorduk Feb 2, 2026
22f778b
style checks
akifcorduk Feb 3, 2026
12c8fcf
fix excluded cliques and fix extended set packing constraints
akifcorduk Feb 3, 2026
5d16246
tests if threre are any complements of a variable in the extended clique
akifcorduk Feb 3, 2026
6319046
fix variables if complements share a clique
akifcorduk Feb 3, 2026
96385fe
add timing
akifcorduk Feb 3, 2026
7cd0a4a
wip
akifcorduk Feb 3, 2026
447713f
fix the knapsack indices
akifcorduk Feb 4, 2026
db8951a
fix weight and set packing issue
akifcorduk Feb 5, 2026
ab2339b
fix obj scale issue
akifcorduk Feb 5, 2026
7e322eb
Merge branch 'main' of github.com:NVIDIA/cuopt into cliques
akifcorduk Feb 6, 2026
d7f6a80
without cliques
akifcorduk Feb 6, 2026
f58b8c5
with cliques
akifcorduk Feb 6, 2026
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
6 changes: 6 additions & 0 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,12 @@ endif()
option(BUILD_MIP_BENCHMARKS "Build MIP benchmarks" OFF)
if(BUILD_MIP_BENCHMARKS AND NOT BUILD_LP_ONLY)
add_executable(solve_MIP ../benchmarks/linear_programming/cuopt/run_mip.cpp)
target_include_directories(solve_MIP
PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/src"
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
)

set_target_properties(solve_MIP
PROPERTIES
Expand Down
19 changes: 19 additions & 0 deletions cpp/src/dual_simplex/sparse_matrix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,12 @@ i_t csr_matrix_t<i_t, f_t>::check_matrix(std::string matrix_name) const
return 0;
}

template <typename i_t, typename f_t>
std::pair<i_t, i_t> csr_matrix_t<i_t, f_t>::get_constraint_range(i_t cstr_idx) const
{
return std::make_pair(this->row_start[cstr_idx], this->row_start[cstr_idx + 1]);
}
Comment on lines +657 to +661
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a debug bounds assert for cstr_idx.

Without a guard, an invalid cstr_idx can read past row_start. A debug assert keeps the fast path while catching misuse early.

🔧 Suggested change (debug-only assert)
 template <typename i_t, typename f_t>
 std::pair<i_t, i_t> csr_matrix_t<i_t, f_t>::get_constraint_range(i_t cstr_idx) const
 {
+  assert(cstr_idx >= 0);
+  assert(static_cast<size_t>(cstr_idx + 1) < this->row_start.size());
   return std::make_pair(this->row_start[cstr_idx], this->row_start[cstr_idx + 1]);
 }
🤖 Prompt for AI Agents
In `@cpp/src/dual_simplex/sparse_matrix.cpp` around lines 578 - 582, Add a
debug-only bounds check at the start of csr_matrix_t<i_t,
f_t>::get_constraint_range to ensure cstr_idx is within [0, row_start.size()-1]
(so that accessing row_start[cstr_idx+1] is safe); use the project's
debug/assert macro (or <cassert> assert) to verify cstr_idx >= 0 and cstr_idx +
1 < this->row_start.size(), then return the pair as before—this keeps the fast
path in release but catches invalid cstr_idx in debug.


// x <- x + alpha * A(:, j)
template <typename i_t, typename f_t>
void scatter_dense(const csc_matrix_t<i_t, f_t>& A, i_t j, f_t alpha, std::vector<f_t>& x)
Expand All @@ -667,6 +673,19 @@ void scatter_dense(const csc_matrix_t<i_t, f_t>& A, i_t j, f_t alpha, std::vecto
}
}

template <typename i_t, typename f_t>
void csr_matrix_t<i_t, f_t>::insert_row(const std::vector<i_t>& vars,
const std::vector<f_t>& coeffs)
{
assert(vars.size() == coeffs.size());
// insert the row into the matrix
this->row_start.push_back(this->row_start.back() + vars.size());
this->m++;
this->nz_max += vars.size();
this->j.insert(this->j.end(), vars.begin(), vars.end());
this->x.insert(this->x.end(), coeffs.begin(), coeffs.end());
}

// x <- x + alpha * A(:, j)
template <typename i_t, typename f_t>
void scatter_dense(const csc_matrix_t<i_t, f_t>& A,
Expand Down
8 changes: 7 additions & 1 deletion cpp/src/dual_simplex/sparse_matrix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,18 @@ class csr_matrix_t {
return true;
}

// get constraint range
std::pair<i_t, i_t> get_constraint_range(i_t cstr_idx) const;

// insert a constraint into the matrix
void insert_row(const std::vector<i_t>& vars, const std::vector<f_t>& coeffs);

i_t nz_max; // maximum number of nonzero entries
i_t m; // number of rows
i_t n; // number of cols
std::vector<i_t> row_start; // row pointers (size m + 1)
std::vector<i_t> j; // column inidices, size nz_max
std::vector<f_t> x; // numerical valuse, size nz_max
std::vector<f_t> x; // numerical values, size nz_max

static_assert(std::is_signed_v<i_t>);
};
Expand Down
1 change: 1 addition & 0 deletions cpp/src/mip/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ set(MIP_NON_LP_FILES
${CMAKE_CURRENT_SOURCE_DIR}/presolve/multi_probe.cu
${CMAKE_CURRENT_SOURCE_DIR}/presolve/probing_cache.cu
${CMAKE_CURRENT_SOURCE_DIR}/presolve/trivial_presolve.cu
${CMAKE_CURRENT_SOURCE_DIR}/presolve/conflict_graph/clique_table.cu
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/feasibility_jump.cu
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/feasibility_jump_kernels.cu
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/fj_cpu.cu)
Expand Down
23 changes: 16 additions & 7 deletions cpp/src/mip/diversity/diversity_manager.cu
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
#include "diversity_manager.cuh"

#include <mip/mip_constants.hpp>
#include <mip/presolve/conflict_graph/clique_table.cuh>
#include <mip/presolve/probing_cache.cuh>
#include <mip/presolve/trivial_presolve.cuh>
#include <mip/problem/problem_helpers.cuh>

#include <dual_simplex/user_problem.hpp>
#include <linear_programming/solve.cuh>

#include <utilities/scope_guard.hpp>
Expand Down Expand Up @@ -200,16 +202,23 @@ bool diversity_manager_t<i_t, f_t>::run_presolve(f_t time_limit)
const bool remap_cache_ids = true;
trivial_presolve(*problem_ptr, remap_cache_ids);
if (!problem_ptr->empty && !check_bounds_sanity(*problem_ptr)) { return false; }
// May overconstrain if Papilo presolve has been run before
if (!context.settings.presolve) {
if (!problem_ptr->empty) {
// do the resizing no-matter what, bounds presolve might not change the bounds but initial
// trivial presolve might have
ls.constraint_prop.bounds_update.resize(*problem_ptr);
if (!context.settings.heuristics_only && !problem_ptr->empty) {
dual_simplex::user_problem_t<i_t, f_t> host_problem(problem_ptr->handle_ptr);
problem_ptr->get_host_user_problem(host_problem);
find_initial_cliques(host_problem, context.settings.tolerances);
problem_ptr->set_constraints_from_host_user_problem(host_problem);
trivial_presolve(*problem_ptr, remap_cache_ids);
}
if (!problem_ptr->empty) {
// do the resizing no-matter what, bounds presolve might not change the bounds but
// initial trivial presolve might have
ls.constraint_prop.bounds_update.resize(*problem_ptr);
// May overconstrain if Papilo presolve has been run before
if (!context.settings.presolve) {
ls.constraint_prop.conditional_bounds_update.update_constraint_bounds(
*problem_ptr, ls.constraint_prop.bounds_update);
if (!check_bounds_sanity(*problem_ptr)) { return false; }
}
if (!check_bounds_sanity(*problem_ptr)) { return false; }
Comment on lines +205 to +221
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find bounds_update class/struct definition
rg -n "class.*bounds_update|struct.*bounds_update" --type-add='cu:*.cu' --type-add='cuh:*.cuh' -A 10

Repository: NVIDIA/cuopt

Length of output: 2672


🏁 Script executed:

# Search for resize method in bounds_update context
rg -n "bounds_update.*resize|\.resize\(" --type-add='cu:*.cu' --type-add='cuh:*.cuh' --type-add='hpp:*.hpp' --type-add='h:*.h' -B 2 -A 3

Repository: NVIDIA/cuopt

Length of output: 50369


🏁 Script executed:

# Find set_constraints_from_host_user_problem implementation
rg -n "set_constraints_from_host_user_problem" --type-add='cu:*.cu' --type-add='cuh:*.cuh' --type-add='hpp:*.hpp' --type-add='h:*.h' -A 5

Repository: NVIDIA/cuopt

Length of output: 2583


🏁 Script executed:

# Check trivial_presolve function behavior
rg -n "void trivial_presolve|void.*trivial_presolve" --type-add='cu:*.cu' --type-add='cuh:*.cuh' --type-add='hpp:*.hpp' --type-add='h:*.h' -A 8

Repository: NVIDIA/cuopt

Length of output: 1556


🏁 Script executed:

# Find bounds_update class and resize method
rg -n "class bounds_update_data_t|void.*resize" cpp/src/mip/presolve/bounds_update_data.cuh -A 15

Repository: NVIDIA/cuopt

Length of output: 328


🏁 Script executed:

# Search for bounds_update resize implementations
rg -n "void resize" cpp/src/mip/presolve/bounds_update_data.cuh cpp/src/mip/presolve/load_balanced_bounds_presolve.cuh -B 2 -A 10

Repository: NVIDIA/cuopt

Length of output: 780


🏁 Script executed:

# Look for bounds_update in constraint_prop context
rg -n "bounds_update.*resize|constraint_prop.*bounds_update" cpp/src/mip/local_search/rounding/constraint_prop.cu -B 3 -A 3

Repository: NVIDIA/cuopt

Length of output: 435


🏁 Script executed:

# Check the file at diversity_manager.cu to see the exact context
sed -n '205,225p' cpp/src/mip/diversity/diversity_manager.cu

Repository: NVIDIA/cuopt

Length of output: 1192


🏁 Script executed:

# Find the bounds_update_data_t::resize implementation
rg -n "bounds_update_data_t.*resize" cpp/src/mip/presolve/bounds_update_data.cu -A 20

Repository: NVIDIA/cuopt

Length of output: 1215


🏁 Script executed:

# Also check what init_changed_constraints does
rg -n "init_changed_constraints" cpp/src/mip/presolve/bounds_update_data.cu -A 10

Repository: NVIDIA/cuopt

Length of output: 573


🏁 Script executed:

# Check prepare_for_next_iteration
rg -n "prepare_for_next_iteration" cpp/src/mip/presolve/bounds_update_data.cu -A 10

Repository: NVIDIA/cuopt

Length of output: 551


🏁 Script executed:

# Look for any reset or clear calls in bounds_update context
rg -n "bounds_changed|min_activity|max_activity" cpp/src/mip/presolve/bounds_update_data.cu -B 2 -A 2

Repository: NVIDIA/cuopt

Length of output: 1216


🏁 Script executed:

# Check what update_constraint_bounds does with the bounds_update data
rg -n "update_constraint_bounds" cpp/src/mip/local_search/rounding/constraint_prop.cuh cpp/src/mip/local_search/rounding/constraint_prop.cu -A 15

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

# Check if there's any initialization between resize and update_constraint_bounds
sed -n '212,220p' cpp/src/mip/diversity/diversity_manager.cu

Repository: NVIDIA/cuopt

Length of output: 515


🏁 Script executed:

# Look for where init_changed_constraints is called
rg -n "init_changed_constraints" cpp/src/mip -r -B 2 -A 2

Repository: NVIDIA/cuopt

Length of output: 2421


🏁 Script executed:

# Check bounds_update constructor and what values it initializes
sed -n '15,28p' cpp/src/mip/presolve/bounds_update_data.cu

Repository: NVIDIA/cuopt

Length of output: 795


Reinitialize bounds_update cached state after constraint replacement.

After set_constraints_from_host_user_problem() modifies the constraint matrix and trivial_presolve() completes, the bounds_update.resize() call only adjusts vector dimensions but does not reinitialize cached computational state. The min_activity, max_activity, and bounds_changed data contain stale values from the previous constraint structure. Call init_changed_constraints() after resize() to reset the activity tracking and ensure update_constraint_bounds() computes from a clean state.

🤖 Prompt for AI Agents
In `@cpp/src/mip/diversity/diversity_manager.cu` around lines 205 - 221, After
replacing constraints via set_constraints_from_host_user_problem() and running
trivial_presolve(), bounds_update.resize(*problem_ptr) only adjusts container
sizes but leaves cached state (min_activity, max_activity, bounds_changed)
stale; call ls.constraint_prop.bounds_update.init_changed_constraints()
immediately after the resize so activity tracking is reinitialized before
calling
ls.constraint_prop.conditional_bounds_update.update_constraint_bounds(*problem_ptr,
ls.constraint_prop.bounds_update); this ensures update_constraint_bounds()
computes against a clean state after
set_constraints_from_host_user_problem()/trivial_presolve().

}
stats.presolve_time = presolve_timer.elapsed_time();
lp_optimal_solution.resize(problem_ptr->n_variables, problem_ptr->handle_ptr->get_stream());
Expand Down
11 changes: 10 additions & 1 deletion cpp/src/mip/local_search/local_search.cu
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,16 @@ bool local_search_t<i_t, f_t>::do_fj_solve(solution_t<i_t, f_t>& solution,
if (time_limit == 0.) return solution.get_feasible();

timer_t timer(time_limit);

// in case this is the first time run, resize
if (in_fj.cstr_weights.size() != (size_t)solution.problem_ptr->n_constraints) {
in_fj.cstr_weights.resize(solution.problem_ptr->n_constraints,
solution.handle_ptr->get_stream());
// reset weights since this is most likely the first call
thrust::uninitialized_fill(solution.handle_ptr->get_thrust_policy(),
in_fj.cstr_weights.begin(),
in_fj.cstr_weights.end(),
1.);
}
Comment on lines 156 to 165
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard resize for shrinking and add CUDA error checks.

If cstr_weights shrinks, begin() + old_size is past the new end, which is UB. Also, the new device ops should surface CUDA errors explicitly. Suggested fix:

Proposed fix
-  if (in_fj.cstr_weights.size() != (size_t)solution.problem_ptr->n_constraints) {
-    i_t old_size = in_fj.cstr_weights.size();
-    in_fj.cstr_weights.resize(solution.problem_ptr->n_constraints,
-                              solution.handle_ptr->get_stream());
-    thrust::uninitialized_fill(solution.handle_ptr->get_thrust_policy(),
-                               in_fj.cstr_weights.begin() + old_size,
-                               in_fj.cstr_weights.end(),
-                               1.);
-  }
+  auto new_size = static_cast<size_t>(solution.problem_ptr->n_constraints);
+  auto old_size = in_fj.cstr_weights.size();
+  if (old_size != new_size) {
+    in_fj.cstr_weights.resize(new_size, solution.handle_ptr->get_stream());
+    if (old_size < new_size) {
+      thrust::uninitialized_fill(solution.handle_ptr->get_thrust_policy(),
+                                 in_fj.cstr_weights.begin() + old_size,
+                                 in_fj.cstr_weights.end(),
+                                 1.);
+    }
+    // Use your project’s CUDA_CHECK / equivalent macro here
+    CUOPT_CUDA_CHECK(cudaGetLastError());
+  }

As per coding guidelines: Every CUDA kernel launch and memory operation must have error checking with CUDA_CHECK or equivalent verification.

🤖 Prompt for AI Agents
In `@cpp/src/mip/local_search/local_search.cu` around lines 154 - 163, The current
resize logic for in_fj.cstr_weights can UB when shrinking because
thrust::uninitialized_fill uses begin() + old_size which may be past end; change
the code to only call uninitialized_fill when the new size is larger than
old_size (compute new_size = solution.problem_ptr->n_constraints and if new_size
> old_size then resize and uninitialized_fill from begin()+old_size to end()),
and ensure all device operations and stream usage (resize with
solution.handle_ptr->get_stream(), thrust::uninitialized_fill with
solution.handle_ptr->get_thrust_policy()) are followed by CUDA error checks (use
CUDA_CHECK or the project's equivalent) so kernel/memory errors are surfaced.

auto h_weights = cuopt::host_copy(in_fj.cstr_weights, solution.handle_ptr->get_stream());
auto h_objective_weight = in_fj.objective_weight.value(solution.handle_ptr->get_stream());
for (auto& cpu_fj : ls_cpu_fj) {
Expand Down
Loading
Loading