Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
00ae695
no recombiner
akifcorduk Oct 22, 2025
049d07e
temporary test on obj cut
akifcorduk Oct 23, 2025
b1e7e85
fix few issues
akifcorduk Oct 24, 2025
11d853e
handle weight mismatch
akifcorduk Oct 24, 2025
5570016
ls time always 1 s
akifcorduk Oct 24, 2025
300ad96
fix timer
akifcorduk Oct 24, 2025
6f557c0
shorter fj, longer lp
akifcorduk Oct 24, 2025
b99ed04
best in population, restart fp, longer fj
akifcorduk Oct 24, 2025
d1413c8
return first feas
akifcorduk Oct 24, 2025
60c2620
Revert "return first feas"
akifcorduk Oct 27, 2025
30375e2
only do weights on FJ
akifcorduk Oct 27, 2025
9dc0ace
sqrt diversiyt
akifcorduk Oct 27, 2025
58b7b2a
fix external solutions adding
akifcorduk Oct 28, 2025
28ac0e8
fix cut issues
akifcorduk Oct 28, 2025
50f3d8e
old diversity
akifcorduk Oct 28, 2025
dca499e
run submip with cuts
akifcorduk Oct 29, 2025
aaa1097
add cutting plane to line segment
akifcorduk Oct 29, 2025
a99d0f4
added bounds prop
akifcorduk Oct 29, 2025
550635f
add also FP
akifcorduk Oct 29, 2025
f1c977b
old diversity and no objective cut on population
akifcorduk Oct 30, 2025
de8d2c3
Merge branch 'main' of github.com:NVIDIA/cuopt into obj_cut_at_recomb…
akifcorduk Nov 27, 2025
47c62fa
fix few merge conflicts
akifcorduk Nov 27, 2025
67f5008
handle review comments
akifcorduk Nov 28, 2025
2a72dde
Merge branch 'release/25.12' of github.com:NVIDIA/cuopt into obj_cut_…
akifcorduk Nov 28, 2025
1695286
build run_mip off
akifcorduk Nov 28, 2025
0acb5be
fix lp time and population insert
akifcorduk Dec 1, 2025
aa78fa6
revert timer changes
akifcorduk Dec 1, 2025
7fb2ed5
fix timer
akifcorduk Dec 1, 2025
e10e419
Merge branch 'main' of github.com:NVIDIA/cuopt into obj_cut_at_recomb…
akifcorduk Jan 13, 2026
fd701f6
license precommit runs
akifcorduk Jan 13, 2026
b22e3a4
fix merge conflicts
akifcorduk Jan 13, 2026
6b26c1b
revert lineinfo
akifcorduk Jan 14, 2026
79e71a1
revert cmake pos change
akifcorduk Jan 16, 2026
82a6020
Merge branch 'main' of github.com:NVIDIA/cuopt into obj_cut_at_recomb…
akifcorduk Jan 16, 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
4 changes: 2 additions & 2 deletions benchmarks/linear_programming/cuopt/run_mip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,6 @@ int main(int argc, char* argv[])
double memory_limit = program.get<double>("--memory-limit");
bool track_allocations = program.get<std::string>("--track-allocations")[0] == 't';

if (num_cpu_threads < 0) { num_cpu_threads = omp_get_max_threads() / n_gpus; }

if (program.is_used("--out-dir")) {
out_dir = program.get<std::string>("--out-dir");
result_file = out_dir + "/final_result.csv";
Expand Down Expand Up @@ -421,6 +419,7 @@ int main(int argc, char* argv[])
paths.push_back(entry.path());
}
}
if (num_cpu_threads < 0) { num_cpu_threads = omp_get_max_threads() / n_gpus; }
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

Potential division by zero if n_gpus is 0.

While n_gpus defaults to 1, there's no validation preventing a user from passing --n-gpus 0, which would cause undefined behavior here. Consider adding an early validation check.

🛡️ Suggested validation (add after line 367)
if (n_gpus <= 0) {
  std::cerr << "Error: --n-gpus must be >= 1" << std::endl;
  return 1;
}
🤖 Prompt for AI Agents
In @benchmarks/linear_programming/cuopt/run_mip.cpp at line 416, Add an early
validation that ensures n_gpus is >= 1 before it's used to compute
num_cpu_threads (i.e., before the expression that calls omp_get_max_threads() /
n_gpus); if n_gpus <= 0, print a clear error to std::cerr and return a non-zero
exit code so the program aborts cleanly. Locate the check near the
initialization/parse area where n_gpus is set (before the line that assigns
num_cpu_threads using omp_get_max_threads()) and ensure the validation prevents
the subsequent division by zero.

// if batch_num is given, trim the paths to only concerned batch
if (batch_num != -1) {
if (n_batches <= 0) {
Expand Down Expand Up @@ -487,6 +486,7 @@ int main(int argc, char* argv[])
}
merge_result_files(out_dir, result_file, n_gpus, batch_num);
} else {
if (num_cpu_threads < 0) { num_cpu_threads = omp_get_max_threads(); }
auto memory_resource = make_async();
if (memory_limit > 0) {
auto limiting_adaptor =
Expand Down
2 changes: 1 addition & 1 deletion cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ if(CMAKE_COMPILER_IS_GNUCXX)
list(APPEND CUOPT_CXX_FLAGS -Werror -Wno-error=deprecated-declarations)
endif(CMAKE_COMPILER_IS_GNUCXX)


# To use sanitizer with cuda runtime, one must follow a few steps:
# 1. Run the binary with env var set: LD_PRELOAD="$(gcc -print-file-name=libasan.so)" ASAN_OPTIONS='protect_shadow_gap=0:replace_intrin=0'
# 2. (Optional) To run with a debugger (gdb or cuda-gdb) use the additional ASAN option alloc_dealloc_mismatch=0
Expand Down Expand Up @@ -178,7 +179,6 @@ if(DEFINE_ASSERT)
add_definitions(-UNDEBUG)
endif()


# ##################################################################################################
# - find CPM based dependencies ------------------------------------------------------------------
rapids_cpm_init()
Expand Down
13 changes: 10 additions & 3 deletions cpp/src/mip/diversity/diversity_manager.cu
Original file line number Diff line number Diff line change
Expand Up @@ -495,8 +495,7 @@ void diversity_manager_t<i_t, f_t>::diversity_step(i_t max_iterations_without_im
improved = false;
while (k-- > 0) {
if (check_b_b_preemption()) { return; }
auto new_sol_vector = population.get_external_solutions();
recombine_and_ls_with_all(new_sol_vector);
population.add_external_solutions_to_population();
population.adjust_weights_according_to_best_feasible();
cuopt_assert(population.test_invariant(), "");
if (population.current_size() < 2) {
Expand Down Expand Up @@ -649,7 +648,15 @@ diversity_manager_t<i_t, f_t>::recombine_and_local_search(solution_t<i_t, f_t>&
ls_config_t<i_t, f_t> ls_config;
ls_config.best_objective_of_parents = best_objective_of_parents;
ls_config.at_least_one_parent_feasible = at_least_one_parent_feasible;
success = this->run_local_search(offspring, population.weights, timer, ls_config);
offspring.swap_problem_pointers();
population.weights_with_cuts.cstr_weights.resize(offspring.problem_ptr->n_constraints,
offspring.handle_ptr->get_stream());
raft::copy(population.weights_with_cuts.cstr_weights.data(),
population.weights.cstr_weights.data(),
population.weights.cstr_weights.size(),
offspring.handle_ptr->get_stream());
Comment on lines +652 to +657
Copy link
Contributor

Choose a reason for hiding this comment

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

I am a little worried about maintaining the two weights arrays as well. I fear it would be easy to end up with broken invariants
Could we maybe imagine a double_buffer_t or flip_buffer_t or ping_pong_t class that would take, say,
thrust::pair<problem_t*, rmm:device_vector> as its type, and allow for swapping between the two sets with the guarantee of no mismatched state?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it makes sense. We should make it robust as more cuts are coming.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The issue here is that problem is member of the solution and weights is the member of population. I think this might cause more invariants to break. I think the solution to all the problems you pointed out is to use uniform problem for all population and diversity management. Do you think we should tackle this now?

success = this->run_local_search(offspring, population.weights_with_cuts, timer, ls_config);
offspring.swap_problem_pointers();
if (!success) {
// add the attempt
mab_recombiner.add_mab_reward(mab_recombiner.last_chosen_option,
Expand Down
83 changes: 36 additions & 47 deletions cpp/src/mip/diversity/population.cu
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand All @@ -24,7 +24,6 @@ constexpr double weight_decrease_ratio = 0.9;
constexpr double max_infeasibility_weight = 1e12;
constexpr double min_infeasibility_weight = 1.;
constexpr double infeasibility_balance_ratio = 1.1;
constexpr double halving_skip_ratio = 0.75;

template <typename i_t, typename f_t>
population_t<i_t, f_t>::population_t(std::string const& name_,
Expand All @@ -41,8 +40,8 @@ population_t<i_t, f_t>::population_t(std::string const& name_,
max_solutions(max_solutions_),
infeasibility_importance(infeasibility_weight_),
weights(0, context.problem_ptr->handle_ptr),
weights_with_cuts(0, context.problem_ptr->handle_ptr),
rng(cuopt::seed_generator::get_seed()),
early_exit_primal_generation(false),
population_hash_map(*problem_ptr),
timer(0)
{
Expand All @@ -64,6 +63,14 @@ i_t get_max_var_threshold(i_t n_vars)
return n_vars - 10;
}

template <typename i_t, typename f_t>
void population_t<i_t, f_t>::apply_problem_ptr_to_all_solutions()
{
for (size_t i = 0; i < indices.size(); i++) {
solutions[indices[i].first].second.problem_with_cuts_ptr = problem_ptr_with_cuts;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we could benefit from adding a flags field to problem_t, one of which could be HasCuts or HasObjectiveCuts. Otherwise I think it will be difficult to check for invariants and ensure we didn't mess up pointer assignments

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is already a flag for the objective cut. We can use that.

}
}

template <typename i_t, typename f_t>
void population_t<i_t, f_t>::allocate_solutions()
{
Expand All @@ -73,6 +80,20 @@ void population_t<i_t, f_t>::allocate_solutions()
}
}

template <typename i_t, typename f_t>
void population_t<i_t, f_t>::set_problem_ptr_with_cuts(problem_t<i_t, f_t>* problem_ptr_with_cuts)
{
constexpr f_t ten = 10.;
this->problem_ptr_with_cuts = problem_ptr_with_cuts;
weights_with_cuts.cstr_weights.resize(problem_ptr_with_cuts->n_constraints,
problem_ptr_with_cuts->handle_ptr->get_stream());
// fill last element with default
thrust::uninitialized_fill(problem_ptr_with_cuts->handle_ptr->get_thrust_policy(),
weights_with_cuts.cstr_weights.begin() + problem_ptr->n_constraints,
weights_with_cuts.cstr_weights.end(),
ten);
}
Comment on lines +83 to +95
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:

# Search for calls to set_problem_ptr_with_cuts
rg -n -B5 -A10 'set_problem_ptr_with_cuts\(' --type cpp --type cu

Repository: NVIDIA/cuopt

Length of output: 82


🏁 Script executed:

# Look at the population class definition and weight initialization
fd -e cu -e cpp -path "*population*" | head -20

Repository: NVIDIA/cuopt

Length of output: 224


🏁 Script executed:

# Search for weights_with_cuts initialization
rg -n 'weights_with_cuts' --type cpp --type cu -A2 -B2

Repository: NVIDIA/cuopt

Length of output: 82


🏁 Script executed:

# Search for calls to set_problem_ptr_with_cuts using corrected syntax
rg -n -B5 -A10 'set_problem_ptr_with_cuts\('

Repository: NVIDIA/cuopt

Length of output: 7170


🏁 Script executed:

# Find population files
fd 'population' -e cu -e cpp -e h -e hpp

Repository: NVIDIA/cuopt

Length of output: 132


🏁 Script executed:

# Search for weights_with_cuts references
rg -n 'weights_with_cuts' -A2 -B2

Repository: NVIDIA/cuopt

Length of output: 4894


🏁 Script executed:

# Let's look at the specific context around the three call sites to understand the flow better
sed -n '540,560p' cpp/src/mip/local_search/local_search.cu

Repository: NVIDIA/cuopt

Length of output: 1021


🏁 Script executed:

# Check the second call site context
sed -n '595,610p' cpp/src/mip/local_search/local_search.cu

Repository: NVIDIA/cuopt

Length of output: 829


🏁 Script executed:

# Check the third call site context
sed -n '645,660p' cpp/src/mip/local_search/local_search.cu

Repository: NVIDIA/cuopt

Length of output: 836


🏁 Script executed:

# Look at the diversity_manager call context
sed -n '645,660p' cpp/src/mip/diversity/diversity_manager.cu

Repository: NVIDIA/cuopt

Length of output: 962


🏁 Script executed:

# Check the constructor/initialization of weights_with_cuts
sed -n '35,50p' cpp/src/mip/diversity/population.cu

Repository: NVIDIA/cuopt

Length of output: 539


🏁 Script executed:

# Check the apply_problem_ptr_to_all_solutions method to understand state changes
rg -n 'apply_problem_ptr_to_all_solutions' -A 20

Repository: NVIDIA/cuopt

Length of output: 10454


🏁 Script executed:

# Check the update_weights method and other weight-related methods
sed -n '180,220p' cpp/src/mip/diversity/population.cu

Repository: NVIDIA/cuopt

Length of output: 1669


🏁 Script executed:

# Check if weights_with_cuts is used before being properly initialized at call site 3
rg -n 'weights_with_cuts' cpp/src/mip/local_search/local_search.cu -B3 -A3 | head -80

Repository: NVIDIA/cuopt

Length of output: 826


🏁 Script executed:

# Verify what happens between fj.copy_weights and set_problem_ptr_with_cuts at line 648-650
sed -n '645,655p' cpp/src/mip/local_search/local_search.cu

Repository: NVIDIA/cuopt

Length of output: 661


🏁 Script executed:

# Let's check if weights_with_cuts is actually used right after set_problem_ptr_with_cuts at call site 3
sed -n '648,700p' cpp/src/mip/local_search/local_search.cu | head -60

Repository: NVIDIA/cuopt

Length of output: 2370


🏁 Script executed:

# Check if there's a pattern where weights are supposed to be copied elsewhere
# Look for update_weights or other weight-related operations
sed -n '117,140p' cpp/src/mip/diversity/population.cu

Repository: NVIDIA/cuopt

Length of output: 1255


🏁 Script executed:

# Check if weights_with_cuts is used after call site 3 and understand the intent
# Look for uses of weights_with_cuts in the local_search context
rg -n 'weights_with_cuts' cpp/src/mip/local_search/local_search.cu -B2 -A2

Repository: NVIDIA/cuopt

Length of output: 626


🏁 Script executed:

# Check what copy_weights does - it should help understand what should happen
rg -n 'copy_weights' -A 15 cpp/src/mip/diversity/

Repository: NVIDIA/cuopt

Length of output: 2107


🏁 Script executed:

# Let's understand the code flow at call site 3 more clearly
# Check what happens right before and after set_problem_ptr_with_cuts at line 650
sed -n '643,655p' cpp/src/mip/local_search/local_search.cu

Repository: NVIDIA/cuopt

Length of output: 771


🏁 Script executed:

# Now let's verify: does weights_with_cuts actually need the old constraint weights?
# Check how it's used after being set
rg -n 'weights_with_cuts' cpp/src/mip/diversity/ cpp/src/mip/local_search/ -B1 -A1 | grep -E '(run_local_search|update_weights|diversity_step)'

Repository: NVIDIA/cuopt

Length of output: 197


🏁 Script executed:

# Check the third call site more carefully - look for any subsequent copy operations
sed -n '645,670p' cpp/src/mip/local_search/local_search.cu

Repository: NVIDIA/cuopt

Length of output: 1338


🏁 Script executed:

# Let's check what happens in the loop after set_problem_ptr_with_cuts at call site 3
# Specifically, does weights_with_cuts get used before being properly initialized?
sed -n '630,670p' cpp/src/mip/local_search/local_search.cu | head -40

Repository: NVIDIA/cuopt

Length of output: 2151


🏁 Script executed:

# Let's trace the full context of whether weights_with_cuts is initialized at call site 3
# Check if there's any copy between fj.copy_weights and the loop that uses weights_with_cuts
sed -n '643,680p' cpp/src/mip/local_search/local_search.cu

Repository: NVIDIA/cuopt

Length of output: 1837


🏁 Script executed:

# Search for where weights_with_cuts is actually used in this context
rg -n 'weights_with_cuts' cpp/src/mip/local_search/ -C 5

Repository: NVIDIA/cuopt

Length of output: 2062


🏁 Script executed:

# Let's verify the issue by checking what resize does to the existing data
# and understand the memory state more clearly
rg -n 'resize\(' cpp/src/mip/diversity/population.cu -B2 -A2

Repository: NVIDIA/cuopt

Length of output: 1043


🏁 Script executed:

# Check if there's any documentation or comments about weights_with_cuts initialization
rg -n 'weights_with_cuts' cpp/src/mip/diversity/population.cuh -B1 -A1

Repository: NVIDIA/cuopt

Length of output: 184


🏁 Script executed:

# Confirm the exact problem: at call site 3, is weights_with_cuts actually used?
# Check if it's used in the subsequent code path
sed -n '650,680p' cpp/src/mip/local_search/local_search.cu

Repository: NVIDIA/cuopt

Length of output: 1398


🏁 Script executed:

# Let's check if weights_with_cuts is used anywhere in the fp loop after call site 3
rg -n 'weights_with_cuts' cpp/src/mip/local_search/local_search.cu -B5 -A5

Repository: NVIDIA/cuopt

Length of output: 1160


🏁 Script executed:

# Check what resize_to_new_problem does and if it uses weights_with_cuts
rg -n 'resize_to_new_problem' cpp/src/mip/local_search/local_search.cu -A 20 | head -50

Repository: NVIDIA/cuopt

Length of output: 2615


🏁 Script executed:

# Let's check the weight_t class to understand what resize does
fd -e h -e hpp | xargs grep -l 'class weight_t' | head -3

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

# Let's check the weight_t class definition to understand the resize behavior
fd weight_t -e h -e hpp -e cu | head -5

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

# Search for weight_t definition
rg -n 'class weight_t\|struct weight_t' -A 10

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

# Search for weight_t definition more broadly
rg 'weight_t' -A 5 cpp/src/mip/diversity/ | grep -E '(struct|class|resize)' | head -20

Repository: NVIDIA/cuopt

Length of output: 175


🏁 Script executed:

# Let's check the actual behavior: when resize is called on an existing vector
# The key question is whether the existing data is preserved or overwritten
# Let's look at the cstr_weights member
rg -n 'cstr_weights' cpp/src/mip/diversity/ -B2 -A2 | head -50

Repository: NVIDIA/cuopt

Length of output: 4275


🏁 Script executed:

# Critical check: at call site 3 (line 650), after resize, what are the first problem_ptr->n_constraints elements?
# They should either be: (1) preserved from before, or (2) uninitialized
# Let's verify by checking if there's any copy_weights or similar before the loop
sed -n '630,660p' cpp/src/mip/local_search/local_search.cu

Repository: NVIDIA/cuopt

Length of output: 1704


🏁 Script executed:

# Now let's check the weight_t struct to understand the resize behavior
cat cpp/src/mip/diversity/weights.cuh | head -100

Repository: NVIDIA/cuopt

Length of output: 1069


🏁 Script executed:

# Check if weights_with_cuts is used after call site 3 in any way
rg -n 'population_ptr->weights_with_cuts\|population\.weights_with_cuts' cpp/src/mip/local_search/ -B2 -A2

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

# Let's check what rmm::device_uvector::resize does
# And let's verify whether weights_with_cuts is actually used after call site 3
rg -n 'reset_alpha_and_save_solution' cpp/src/mip/local_search/local_search.cu -A 5

Repository: NVIDIA/cuopt

Length of output: 1537


🏁 Script executed:

# Look for the actual issue: at call site 3, after set_problem_ptr_with_cuts,
# is there any guarantee that the first problem_ptr->n_constraints elements are initialized?
# Let's check the state of weights_with_cuts before each call site
sed -n '540,565p' cpp/src/mip/local_search/local_search.cu

Repository: NVIDIA/cuopt

Length of output: 1216


🏁 Script executed:

# Final verification: Check the state at call site 3 - is weights_with_cuts used later in the loop?
sed -n '650,700p' cpp/src/mip/local_search/local_search.cu | grep -i weight

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

# Check if there's supposed to be a copy similar to call sites 1 & 2
# Look at the pattern in call sites 1 and 2 right after set_problem_ptr_with_cuts
sed -n '545,562p' cpp/src/mip/local_search/local_search.cu

Repository: NVIDIA/cuopt

Length of output: 974


Add raft::copy call after set_problem_ptr_with_cuts at line 650 to initialize constraint weights.

At line 650, set_problem_ptr_with_cuts() resizes weights_with_cuts but only fills the newly added cutting plane constraints. Unlike the other two call sites (lines 546 and 598), there is no subsequent raft::copy() to populate the first problem_ptr->n_constraints elements, leaving them uninitialized. The comment at line 646-648 mentions copying weights, but it only updates weights, not weights_with_cuts. Add:

raft::copy(population_ptr->weights_with_cuts.cstr_weights.data(),
           fj.cstr_weights.data(),
           fj.cstr_weights.size(),
           solution.handle_ptr->get_stream());

after line 650 to match the pattern used at the other call sites.

🤖 Prompt for AI Agents
In @cpp/src/mip/diversity/population.cu around lines 83 - 95, The call to
set_problem_ptr_with_cuts resizes weights_with_cuts.cstr_weights but does not
copy the original constraint weights into the first problem_ptr->n_constraints
slots, leaving them uninitialized; after the resize/fill in
population_t<i_t,f_t>::set_problem_ptr_with_cuts (or immediately after the call
site where population_ptr is used) add a raft::copy from the original
fj.cstr_weights (or population_ptr->weights) into
population_ptr->weights_with_cuts.cstr_weights for the first
fj.cstr_weights.size() elements and use solution.handle_ptr->get_stream() (or
problem_ptr_with_cuts->handle_ptr->get_stream()) for the stream to match the
other call sites.


template <typename i_t, typename f_t>
void population_t<i_t, f_t>::initialize_population()
{
Expand All @@ -87,6 +108,12 @@ void population_t<i_t, f_t>::initialize_population()
weights.cstr_weights.begin(),
weights.cstr_weights.end(),
ten);
weights_with_cuts.cstr_weights.resize(problem_ptr->n_constraints,
problem_ptr->handle_ptr->get_stream());
thrust::uninitialized_fill(problem_ptr->handle_ptr->get_thrust_policy(),
weights_with_cuts.cstr_weights.begin(),
weights_with_cuts.cstr_weights.end(),
ten);
}

template <typename i_t, typename f_t>
Expand All @@ -109,12 +136,11 @@ std::pair<solution_t<i_t, f_t>, solution_t<i_t, f_t>> population_t<i_t, f_t>::ge
auto second_solution = solutions[indices[j].first].second;
// if best feasible and best are the same, take the second index instead of best
if (i == 0 && j == 1) {
bool same =
check_integer_equal_on_indices(first_solution.problem_ptr->integer_indices,
first_solution.assignment,
second_solution.assignment,
first_solution.problem_ptr->tolerances.integrality_tolerance,
first_solution.handle_ptr);
bool same = check_integer_equal_on_indices(problem_ptr->integer_indices,
first_solution.assignment,
second_solution.assignment,
problem_ptr->tolerances.integrality_tolerance,
first_solution.handle_ptr);
if (same) {
auto new_sol = solutions[indices[2].first].second;
second_solution = std::move(new_sol);
Expand Down Expand Up @@ -172,7 +198,6 @@ void population_t<i_t, f_t>::add_external_solution(const std::vector<f_t>& solut
CUOPT_LOG_DEBUG("Found new best solution %g in external queue",
problem_ptr->get_user_obj_from_solver_obj(objective));
}
if (external_solution_queue.size() >= 5) { early_exit_primal_generation = true; }
solutions_in_external_queue_ = true;
}

Expand All @@ -184,6 +209,7 @@ void population_t<i_t, f_t>::add_external_solutions_to_population()

auto new_sol_vector = get_external_solutions();
add_solutions_from_vec(std::move(new_sol_vector));
apply_problem_ptr_to_all_solutions();
}

// normally we would need a lock here but these are boolean types and race conditions are not
Expand All @@ -192,7 +218,6 @@ template <typename i_t, typename f_t>
void population_t<i_t, f_t>::preempt_heuristic_solver()
{
context.preempt_heuristic_solver_ = true;
early_exit_primal_generation = true;
}

template <typename i_t, typename f_t>
Expand Down Expand Up @@ -668,42 +693,6 @@ std::vector<solution_t<i_t, f_t>> population_t<i_t, f_t>::population_to_vector()
return sol_vec;
}

template <typename i_t, typename f_t>
void population_t<i_t, f_t>::halve_the_population()
{
raft::common::nvtx::range fun_scope("halve_the_population");
// try 3/4 here
if (current_size() <= (max_solutions * halving_skip_ratio)) { return; }
CUOPT_LOG_DEBUG("Halving the population, current size: %lu", current_size());
// put population into a vector
auto sol_vec = population_to_vector();
i_t counter = 0;
constexpr i_t max_adjustments = 4;
size_t max_var_threshold = get_max_var_threshold(problem_ptr->n_integer_vars);

std::lock_guard<std::recursive_mutex> lock(write_mutex);
while (current_size() > max_solutions / 2) {
clear_except_best_feasible();
var_threshold = std::max(var_threshold * 0.97, 0.5 * problem_ptr->n_integer_vars);
for (auto& sol : sol_vec) {
add_solution(solution_t<i_t, f_t>(sol));
}
if (counter++ > max_adjustments) break;
}
counter = 0;
// if we removed too many decrease the diversity a little
while (current_size() < max_solutions / 4) {
clear_except_best_feasible();
var_threshold = std::min(
max_var_threshold,
std::min((size_t)(var_threshold * 1.02), (size_t)(0.995 * problem_ptr->n_integer_vars)));
for (auto& sol : sol_vec) {
add_solution(solution_t<i_t, f_t>(sol));
}
if (counter++ > max_adjustments) break;
}
}

template <typename i_t, typename f_t>
size_t population_t<i_t, f_t>::find_free_solution_index()
{
Expand Down
10 changes: 7 additions & 3 deletions cpp/src/mip/diversity/population.cuh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand Down Expand Up @@ -151,7 +151,6 @@ class population_t {
void find_diversity(std::vector<solution_t<i_t, f_t>>& initial_sol_vector, bool avg);

std::vector<solution_t<i_t, f_t>> population_to_vector();
void halve_the_population();

void run_solution_callbacks(solution_t<i_t, f_t>& sol);

Expand All @@ -161,6 +160,9 @@ class population_t {

void diversity_step(i_t max_iterations_without_improvement);

void set_problem_ptr_with_cuts(problem_t<i_t, f_t>* problem_ptr_with_cuts);
void apply_problem_ptr_to_all_solutions();

// does some consistency tests
bool test_invariant();

Expand All @@ -169,6 +171,7 @@ class population_t {
std::string name;
mip_solver_context_t<i_t, f_t>& context;
problem_t<i_t, f_t>* problem_ptr;
problem_t<i_t, f_t>* problem_ptr_with_cuts;
diversity_manager_t<i_t, f_t>& dm;
i_t var_threshold;
i_t initial_threshold;
Expand All @@ -178,6 +181,7 @@ class population_t {
f_t infeasibility_importance = 100.;
size_t max_solutions;
weight_t<i_t, f_t> weights;
weight_t<i_t, f_t> weights_with_cuts;
std::vector<std::pair<size_t, f_t>> indices;
std::vector<std::pair<bool, solution_t<i_t, f_t>>> solutions;

Expand All @@ -202,7 +206,7 @@ class population_t {
i_t update_iter = 0;
std::recursive_mutex write_mutex;
std::mutex solution_mutex;
std::atomic<bool> early_exit_primal_generation = false;
std::atomic<bool> preempt_heuristic_solver_ = false;
std::atomic<bool> solutions_in_external_queue_ = false;
f_t best_feasible_objective = std::numeric_limits<f_t>::max();
assignment_hash_map_t<i_t, f_t> population_hash_map;
Expand Down
9 changes: 6 additions & 3 deletions cpp/src/mip/diversity/recombiners/bound_prop_recombiner.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class bound_prop_recombiner_t : public recombiner_t<i_t, f_t> {
auto& other_solution = a.get_feasible() ? b : a;
// copy the solution from guiding
solution_t<i_t, f_t> offspring(guiding_solution);
offspring.swap_problem_pointers();
// find same values and populate it to offspring
i_t n_different_vars = this->assign_same_integer_values(a, b, offspring);
CUOPT_LOG_DEBUG("BP rec: Number of different variables %d MAX_VARS %d",
Expand Down Expand Up @@ -181,8 +182,9 @@ class bound_prop_recombiner_t : public recombiner_t<i_t, f_t> {
rmm::device_uvector<f_t> old_assignment(offspring.assignment,
offspring.handle_ptr->get_stream());
offspring.handle_ptr->sync_stream();
offspring.assignment = std::move(fixed_assignment);
offspring.problem_ptr = &fixed_problem;
offspring.assignment = std::move(fixed_assignment);
problem_t<i_t, f_t>* orig_problem_ptr = offspring.problem_ptr;
offspring.problem_ptr = &fixed_problem;
cuopt_func_call(offspring.test_variable_bounds(false));
get_probing_values_for_feasible(guiding_solution,
other_solution,
Expand All @@ -199,7 +201,7 @@ class bound_prop_recombiner_t : public recombiner_t<i_t, f_t> {
constraint_prop.single_rounding_only = false;
cuopt_func_call(bool feasible_after_bounds_prop = offspring.get_feasible());
offspring.handle_ptr->sync_stream();
offspring.problem_ptr = a.problem_ptr;
offspring.problem_ptr = orig_problem_ptr;
fixed_assignment = std::move(offspring.assignment);
offspring.assignment = std::move(old_assignment);
offspring.handle_ptr->sync_stream();
Expand All @@ -219,6 +221,7 @@ class bound_prop_recombiner_t : public recombiner_t<i_t, f_t> {
}
constraint_prop.max_n_failed_repair_iterations = 1;
cuopt_func_call(offspring.test_number_all_integer());
offspring.swap_problem_pointers();
bool better_cost_than_parents =
offspring.get_quality(weights) <
std::min(other_solution.get_quality(weights), guiding_solution.get_quality(weights));
Expand Down
4 changes: 3 additions & 1 deletion cpp/src/mip/diversity/recombiners/fp_recombiner.cuh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand Down Expand Up @@ -44,6 +44,7 @@ class fp_recombiner_t : public recombiner_t<i_t, f_t> {
auto& other_solution = a.get_feasible() ? b : a;
// copy the solution from A
solution_t<i_t, f_t> offspring(guiding_solution);
offspring.swap_problem_pointers();
// find same values and populate it to offspring
i_t n_different_vars =
this->assign_same_integer_values(guiding_solution, other_solution, offspring);
Expand Down Expand Up @@ -126,6 +127,7 @@ class fp_recombiner_t : public recombiner_t<i_t, f_t> {
fp_recombiner_config_t::decrease_max_n_of_vars_from_other();
}
}
offspring.swap_problem_pointers();
bool better_cost_than_parents =
offspring.get_quality(weights) <
std::min(other_solution.get_quality(weights), guiding_solution.get_quality(weights));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand Down Expand Up @@ -75,6 +75,7 @@ class line_segment_recombiner_t : public recombiner_t<i_t, f_t> {
auto& other_solution = a.get_feasible() ? b : a;
// copy the solution from A
solution_t<i_t, f_t> offspring(guiding_solution);
offspring.swap_problem_pointers();
timer_t line_segment_timer{ls_recombiner_config_t::time_limit};
// TODO after we have the conic combination, detect the lambda change
// (i.e. the integral variables flip on line segment)
Expand All @@ -99,6 +100,7 @@ class line_segment_recombiner_t : public recombiner_t<i_t, f_t> {
is_feasibility_run,
line_segment_timer);
line_segment_search.settings = {};
offspring.swap_problem_pointers();
bool better_cost_than_parents =
offspring.get_quality(weights) <
std::min(other_solution.get_quality(weights), guiding_solution.get_quality(weights));
Expand Down
Loading