Skip to content
Draft
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
10 changes: 8 additions & 2 deletions cpp/src/branch_and_bound/branch_and_bound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3268,6 +3268,12 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
if ((settings_.clique_cuts != 0 || settings_.zero_half_cuts != 0) && clique_table_ == nullptr &&
omp_get_num_threads() >= CUOPT_MIP_CLIQUE_CUTS_REQUIRED_THREAD_COUNT) {
signal_extend_cliques_.store(false, std::memory_order_release);
mip::clique_config_t clique_config;
clique_table_ =
std::make_shared<mip::clique_table_t<i_t, f_t>>(2 * original_problem_.num_cols,
clique_config.min_clique_size,
clique_config.max_clique_size_for_extension);
auto* initial_clique_table = clique_table_.get();
typename mip_solver_settings_t<i_t, f_t>::tolerances_t tolerances_for_clique{};
tolerances_for_clique.presolve_absolute_tolerance = settings_.primal_tol;
tolerances_for_clique.absolute_tolerance = settings_.primal_tol;
Expand All @@ -3277,12 +3283,12 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
tolerances_for_clique.relative_mip_gap = settings_.relative_mip_gap_tol;

#pragma omp task priority(CUOPT_DEFAULT_TASK_PRIORITY) depend(out : *clique_signal) \
firstprivate(tolerances_for_clique)
firstprivate(tolerances_for_clique, initial_clique_table)
{
user_problem_t<i_t, f_t> problem_copy = original_problem_;
timer_t timer(std::numeric_limits<double>::infinity());
mip::find_initial_cliques(
problem_copy, tolerances_for_clique, clique_table_, timer, clique_signal);
problem_copy, tolerances_for_clique, *initial_clique_table, timer, clique_signal);
}
}

Expand Down
28 changes: 10 additions & 18 deletions cpp/src/mip_heuristics/presolve/conflict_graph/clique_table.cu
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ void print_clique_table(const clique_table_t<i_t, f_t>& clique_table)
template <typename i_t, typename f_t>
void find_initial_cliques(user_problem_t<i_t, f_t>& problem,
typename mip_solver_settings_t<i_t, f_t>::tolerances_t tolerances,
std::shared_ptr<clique_table_t<i_t, f_t>>& clique_table_out,
clique_table_t<i_t, f_t>& clique_table,
cuopt::timer_t& timer,
omp_atomic_t<bool>* signal_extend)
{
Expand Down Expand Up @@ -701,48 +701,40 @@ void find_initial_cliques(user_problem_t<i_t, f_t>& problem,
t_sort = stage_timer.elapsed_time();
#endif
clique_config_t clique_config;
auto clique_table =
std::make_shared<clique_table_t<i_t, f_t>>(2 * problem.num_cols,
clique_config.min_clique_size,
clique_config.max_clique_size_for_extension);
clique_table->tolerances = tolerances;
clique_table.tolerances = tolerances;
double time_limit_for_additional_cliques = timer.remaining_time() / 2;
cuopt::timer_t additional_cliques_timer(time_limit_for_additional_cliques);
double find_work_estimate = 0.0;
// Always build base cliques in full; signal_extend only gates the extension phase.
for (const auto& knapsack_constraint : knapsack_constraints) {
if (timer.check_time_limit()) { break; }
find_cliques_from_constraint(knapsack_constraint, *clique_table, additional_cliques_timer);
find_cliques_from_constraint(knapsack_constraint, clique_table, additional_cliques_timer);
find_work_estimate += knapsack_constraint.entries.size();
}
#ifdef DEBUG_CLIQUE_TABLE
t_find = stage_timer.elapsed_time();
#endif
CUOPT_LOG_DEBUG("Number of cliques: %d, additional cliques: %d, find_work=%.0f",
clique_table->first.size(),
clique_table->addtl_cliques.size(),
clique_table.first.size(),
clique_table.addtl_cliques.size(),
find_work_estimate);
remove_small_cliques(*clique_table, timer);
remove_small_cliques(clique_table, timer);
#ifdef DEBUG_CLIQUE_TABLE
t_small = stage_timer.elapsed_time();
#endif
fill_var_clique_maps(*clique_table);
fill_var_clique_maps(clique_table);
#ifdef DEBUG_CLIQUE_TABLE
t_maps = stage_timer.elapsed_time();
#endif
// Publish the base table so cut generation can start using it; the extension
// phase below keeps mutating *clique_table, so the consumer must signal this
// task to stop and join it (taskwait) before reading the table.
clique_table_out = clique_table;
double extend_work = 0.0;
i_t n_extended_cliques = extend_cliques(knapsack_constraints,
*clique_table,
clique_table,
timer,
&extend_work,
clique_config.min_extend_work,
clique_config.max_extend_work,
signal_extend);
if (n_extended_cliques > 0) { fill_var_clique_maps(*clique_table); }
if (n_extended_cliques > 0) { fill_var_clique_maps(clique_table); }
#ifdef DEBUG_CLIQUE_TABLE
t_extend = stage_timer.elapsed_time();
CUOPT_LOG_DEBUG(
Expand All @@ -765,7 +757,7 @@ void find_initial_cliques(user_problem_t<i_t, f_t>& problem,
template void find_initial_cliques<int, F_TYPE>( \
user_problem_t<int, F_TYPE> & problem, \
typename mip_solver_settings_t<int, F_TYPE>::tolerances_t tolerances, \
std::shared_ptr<clique_table_t<int, F_TYPE>> & clique_table_out, \
clique_table_t<int, F_TYPE> & clique_table, \
cuopt::timer_t & timer, \
omp_atomic_t<bool> * signal_extend); \
template void build_clique_table<int, F_TYPE>( \
Expand Down
12 changes: 5 additions & 7 deletions cpp/src/mip_heuristics/presolve/conflict_graph/clique_table.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -201,16 +201,14 @@ struct clique_table_t {
typename mip_solver_settings_t<i_t, f_t>::tolerances_t tolerances;
};

// Builds the conflict-graph clique table for `problem`. The base cliques are
// published to `clique_table_out` before the (optional, signal-gated) extension
// phase begins, so cut generation can pick up the table while extension keeps
// running concurrently. Consumers MUST set `*signal_extend` and join the
// producing task before reading the table (see prepare_fractional_sub_conflict_graph), since
// the extension phase keeps mutating the same object after it is published.
// Builds the conflict-graph clique table for `problem`. Consumers MUST set
// `*signal_extend` and join the producing task before reading the table (see
// prepare_fractional_sub_conflict_graph), since the extension phase keeps
// mutating the table after its base cliques are built.
template <typename i_t, typename f_t>
void find_initial_cliques(simplex::user_problem_t<i_t, f_t>& problem,
typename mip_solver_settings_t<i_t, f_t>::tolerances_t tolerances,
std::shared_ptr<clique_table_t<i_t, f_t>>& clique_table_out,
clique_table_t<i_t, f_t>& clique_table,
cuopt::timer_t& timer,
omp_atomic_t<bool>* signal_extend = nullptr);

Expand Down
22 changes: 22 additions & 0 deletions cpp/tests/mip/miplib_test.cu
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,28 @@ TEST(mip_solve, low_thread_count_test)
test_variable_bounds(problem, solution.get_solution(), settings);
}

TEST(mip_solve, repeated_concurrent_initial_clique_generation)
{
mip_solver_settings_t<int, double> settings;
settings.num_cpu_threads = 8;
settings.time_limit = 30;
settings.work_limit = 1;
settings.clique_cuts = 1;
settings.zero_half_cuts = 1;

const raft::handle_t handle_{};

auto path = make_path_absolute("mip/gen-ip054.mps");
auto problem = cuopt::mathematical_optimization::io::read_mps<int, double>(path, false);
handle_.sync_stream();

constexpr int num_solves = 25;
for (int solve = 0; solve < num_solves; ++solve) {
auto solution = solve_mip(&handle_, problem, settings);
EXPECT_NE(solution.get_termination_status(), mip_termination_status_t::NoTermination);
}
}

// Verify --node-limit is respected: swath1 normally requires several thousand B&B
// nodes to prove optimality, so capping at 1000 forces the solver to stop early with
// a feasible (but not necessarily optimal) solution.
Expand Down