Skip to content

Commit ed72a4f

Browse files
authored
Merge branch 'main' into recursive-submip
2 parents 428bbe2 + 88b2e02 commit ed72a4f

6 files changed

Lines changed: 294 additions & 78 deletions

File tree

cpp/src/cuts/cuts.cpp

Lines changed: 170 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3201,6 +3201,27 @@ void cut_generation_t<i_t, f_t>::generate_implied_bound_cuts(
32013201
}
32023202
}
32033203

3204+
namespace {
3205+
3206+
// Total probing-edge budget from the byte cap and the remaining work headroom
3207+
// (max_work_estimate - work_estimate).
3208+
template <typename i_t, typename f_t>
3209+
size_t max_probing_edge_budget(size_t num_vertices, f_t work_headroom)
3210+
{
3211+
constexpr size_t max_probing_conflict_bytes = size_t{8} << 30;
3212+
const size_t probing_degree_bytes = num_vertices * sizeof(size_t);
3213+
const size_t probing_edge_bytes =
3214+
sizeof(std::pair<i_t, i_t>) + sizeof(std::pair<f_t, i_t>) + 2 * sizeof(i_t);
3215+
const size_t max_edges_by_memory =
3216+
probing_degree_bytes < max_probing_conflict_bytes
3217+
? (max_probing_conflict_bytes - probing_degree_bytes) / probing_edge_bytes
3218+
: 0;
3219+
const size_t max_edges_by_work = (size_t)std::max<f_t>(0.0, work_headroom / 4.0);
3220+
return std::min(max_edges_by_memory, max_edges_by_work);
3221+
}
3222+
3223+
} // namespace
3224+
32043225
template <typename i_t, typename f_t>
32053226
void cut_generation_t<i_t, f_t>::prepare_fractional_sub_conflict_graph(
32063227
const simplex_solver_settings_t<i_t, f_t>& settings,
@@ -3225,9 +3246,9 @@ void cut_generation_t<i_t, f_t>::prepare_fractional_sub_conflict_graph(
32253246
}
32263247

32273248
if (clique_table_ == nullptr) { return; }
3228-
// small_clique_adj may carry pairwise CG edges from cliques demoted by
3229-
// remove_small_cliques; clique_table_t::empty() accounts for that.
3230-
if (clique_table_->empty()) { return; }
3249+
const bool has_probing_conflicts =
3250+
!probing_implied_bound_.zero_variables.empty() || !probing_implied_bound_.one_variables.empty();
3251+
if (clique_table_->empty() && !has_probing_conflicts) { return; }
32313252

32323253
const i_t num_vars = user_problem_.num_cols;
32333254
cuopt_assert(clique_table_->n_variables == num_vars,
@@ -3236,6 +3257,12 @@ void cut_generation_t<i_t, f_t>::prepare_fractional_sub_conflict_graph(
32363257
"prepare_fractional_sub_conflict_graph xstar size mismatch");
32373258
cuopt_assert(user_problem_.var_types.size() == static_cast<size_t>(num_vars),
32383259
"prepare_fractional_sub_conflict_graph user problem var_types size mismatch");
3260+
if (has_probing_conflicts) {
3261+
cuopt_assert(probing_implied_bound_.zero_offsets.size() == (size_t)num_vars + 1,
3262+
"Probing zero-offset column count mismatch");
3263+
cuopt_assert(probing_implied_bound_.one_offsets.size() == (size_t)num_vars + 1,
3264+
"Probing one-offset column count mismatch");
3265+
}
32393266

32403267
const f_t bound_tol = settings.primal_tol;
32413268
f_t work_estimate = 0.0;
@@ -3311,17 +3338,6 @@ void cut_generation_t<i_t, f_t>::prepare_fractional_sub_conflict_graph(
33113338
adj.push_back(local_neighbor);
33123339
}
33133340
kept_adj_entries += adj.size();
3314-
#ifdef ASSERT_MODE
3315-
{
3316-
std::unordered_set<i_t> adj_global;
3317-
adj_global.reserve(adj.size());
3318-
for (const i_t neighbor : adj) {
3319-
const i_t v = sub_cg_.vertices[neighbor];
3320-
cuopt_assert(adj_global.insert(v).second,
3321-
"Duplicate neighbor in fractional sub-CG adjacency list");
3322-
}
3323-
}
3324-
#endif
33253341
}
33263342
work_estimate += static_cast<f_t>(sub_cg_.vertices.size()) + static_cast<f_t>(total_adj_entries) +
33273343
2.0 * static_cast<f_t>(kept_adj_entries);
@@ -3330,17 +3346,153 @@ void cut_generation_t<i_t, f_t>::prepare_fractional_sub_conflict_graph(
33303346
return;
33313347
}
33323348

3349+
size_t probing_entries_scanned = 0;
3350+
size_t probing_edges_kept = 0;
3351+
if (has_probing_conflicts) {
3352+
using candidate_t = std::pair<f_t, i_t>;
3353+
// Min-heap on score: front() is the worst kept candidate, so a better one replaces it.
3354+
auto better_candidate = [](const candidate_t& a, const candidate_t& b) {
3355+
return a.first > b.first || (a.first == b.first && a.second < b.second);
3356+
};
3357+
3358+
const size_t max_probing_edges =
3359+
max_probing_edge_budget<i_t, f_t>(sub_cg_.vertices.size(), max_work_estimate - work_estimate);
3360+
const size_t max_edges_per_literal =
3361+
max_probing_edges / std::max<size_t>(1, sub_cg_.vertices.size());
3362+
3363+
size_t raw_edge_bound = 0;
3364+
for (i_t j = 0; j < num_vars; ++j) {
3365+
if (!sub_cg_.in_subgraph[j]) { continue; }
3366+
raw_edge_bound +=
3367+
probing_implied_bound_.zero_offsets[j + 1] - probing_implied_bound_.zero_offsets[j];
3368+
raw_edge_bound +=
3369+
probing_implied_bound_.one_offsets[j + 1] - probing_implied_bound_.one_offsets[j];
3370+
}
3371+
3372+
std::vector<std::pair<i_t, i_t>> probing_edges;
3373+
probing_edges.reserve(std::min(max_probing_edges, raw_edge_bound));
3374+
3375+
for (i_t j = 0; j < num_vars; ++j) {
3376+
if (!sub_cg_.in_subgraph[j]) { continue; }
3377+
for (bool one : {false, true}) {
3378+
const auto& offsets =
3379+
one ? probing_implied_bound_.one_offsets : probing_implied_bound_.zero_offsets;
3380+
const auto& variables =
3381+
one ? probing_implied_bound_.one_variables : probing_implied_bound_.zero_variables;
3382+
const auto& lower_bounds =
3383+
one ? probing_implied_bound_.one_lower_bound : probing_implied_bound_.zero_lower_bound;
3384+
const auto& upper_bounds =
3385+
one ? probing_implied_bound_.one_upper_bound : probing_implied_bound_.zero_upper_bound;
3386+
const i_t source = one ? j : j + num_vars;
3387+
const i_t source_local = sub_cg_.vertex_to_local[source];
3388+
const i_t begin = offsets[j];
3389+
const i_t end = offsets[j + 1];
3390+
cuopt_assert(source_local >= 0, "Probing conflict source must be fractional");
3391+
3392+
std::vector<candidate_t> candidates;
3393+
candidates.reserve(std::min(max_edges_per_literal, (size_t)(end - begin)));
3394+
for (i_t p = begin; p < end; ++p) {
3395+
probing_entries_scanned++;
3396+
const i_t target_var = variables[p];
3397+
cuopt_assert(target_var >= 0 && target_var < num_vars,
3398+
"Probing conflict target out of range");
3399+
if (target_var == j) { continue; }
3400+
i_t target = -1;
3401+
if (upper_bounds[p] < 1.0 - settings.integer_tol) {
3402+
target = target_var;
3403+
} else if (lower_bounds[p] > settings.integer_tol) {
3404+
target = target_var + num_vars;
3405+
} else {
3406+
continue;
3407+
}
3408+
if (!sub_cg_.in_subgraph[target]) { continue; }
3409+
const i_t target_local = sub_cg_.vertex_to_local[target];
3410+
candidate_t candidate{sub_cg_.weights[source_local] + sub_cg_.weights[target_local],
3411+
target_local};
3412+
if (candidates.size() < max_edges_per_literal) {
3413+
candidates.push_back(candidate);
3414+
if (candidates.size() == max_edges_per_literal) {
3415+
std::make_heap(candidates.begin(), candidates.end(), better_candidate);
3416+
}
3417+
continue;
3418+
}
3419+
if (max_edges_per_literal == 0) { continue; }
3420+
if (better_candidate(candidate, candidates.front())) {
3421+
std::pop_heap(candidates.begin(), candidates.end(), better_candidate);
3422+
candidates.back() = candidate;
3423+
std::push_heap(candidates.begin(), candidates.end(), better_candidate);
3424+
}
3425+
}
3426+
for (const auto& candidate : candidates) {
3427+
probing_edges.emplace_back(std::min(source_local, candidate.second),
3428+
std::max(source_local, candidate.second));
3429+
}
3430+
}
3431+
if (toc(start_time) >= settings.time_limit) {
3432+
sub_cg_.clear();
3433+
return;
3434+
}
3435+
}
3436+
cuopt_assert(probing_edges.size() <= max_probing_edges,
3437+
"Selected probing conflicts exceed memory budget");
3438+
3439+
std::sort(probing_edges.begin(), probing_edges.end());
3440+
probing_edges.erase(std::unique(probing_edges.begin(), probing_edges.end()),
3441+
probing_edges.end());
3442+
probing_edges_kept = probing_edges.size();
3443+
3444+
std::vector<size_t> probing_degrees(sub_cg_.vertices.size(), 0);
3445+
for (const auto& [a, b] : probing_edges) {
3446+
probing_degrees[a]++;
3447+
probing_degrees[b]++;
3448+
}
3449+
for (size_t i = 0; i < sub_cg_.adj_local.size(); ++i) {
3450+
sub_cg_.adj_local[i].reserve(sub_cg_.adj_local[i].size() + probing_degrees[i]);
3451+
}
3452+
for (const auto& [a, b] : probing_edges) {
3453+
sub_cg_.adj_local[a].push_back(b);
3454+
sub_cg_.adj_local[b].push_back(a);
3455+
}
3456+
}
3457+
3458+
kept_adj_entries = 0;
3459+
for (auto& adj : sub_cg_.adj_local) {
3460+
std::sort(adj.begin(), adj.end());
3461+
adj.erase(std::unique(adj.begin(), adj.end()), adj.end());
3462+
kept_adj_entries += adj.size();
3463+
}
3464+
work_estimate = 4.0 * static_cast<f_t>(num_vars) +
3465+
6.0 * static_cast<f_t>(sub_cg_.vertices.size()) +
3466+
static_cast<f_t>(total_adj_entries) + 2.0 * static_cast<f_t>(kept_adj_entries);
3467+
if (work_estimate > max_work_estimate) {
3468+
sub_cg_.clear();
3469+
return;
3470+
}
3471+
3472+
#ifdef ASSERT_MODE
3473+
for (const auto& adj : sub_cg_.adj_local) {
3474+
cuopt_assert(std::adjacent_find(adj.begin(), adj.end()) == adj.end(),
3475+
"Duplicate neighbor in fractional conflict subgraph");
3476+
}
3477+
#endif
3478+
33333479
sub_cg_.ready = true;
33343480
CLIQUE_CUTS_DEBUG(
3335-
"prepare_fractional_sub_conflict_graph ready vertices=%lld raw_adj=%lld kept_adj=%lld",
3481+
"prepare_fractional_sub_conflict_graph ready vertices=%lld raw_adj=%lld kept_adj=%lld "
3482+
"probing_scanned=%lld probing_kept=%lld",
33363483
static_cast<long long>(sub_cg_.vertices.size()),
33373484
static_cast<long long>(total_adj_entries),
3338-
static_cast<long long>(kept_adj_entries));
3485+
static_cast<long long>(kept_adj_entries),
3486+
static_cast<long long>(probing_entries_scanned),
3487+
static_cast<long long>(probing_edges_kept));
33393488
ZERO_HALF_DEBUG(
3340-
"prepare_fractional_sub_conflict_graph ready vertices=%lld raw_adj=%lld kept_adj=%lld",
3489+
"prepare_fractional_sub_conflict_graph ready vertices=%lld raw_adj=%lld kept_adj=%lld "
3490+
"probing_scanned=%lld probing_kept=%lld",
33413491
static_cast<long long>(sub_cg_.vertices.size()),
33423492
static_cast<long long>(total_adj_entries),
3343-
static_cast<long long>(kept_adj_entries));
3493+
static_cast<long long>(kept_adj_entries),
3494+
static_cast<long long>(probing_entries_scanned),
3495+
static_cast<long long>(probing_edges_kept));
33443496
}
33453497

33463498
template <typename i_t, typename f_t>

cpp/src/mip_heuristics/diversity/diversity_manager.cu

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,10 @@ void diversity_manager_t<i_t, f_t>::add_user_given_solutions(
239239
}
240240

241241
if (problem_ptr->pre_process_assignment(init_sol_assignment)) {
242+
raft::copy(sol.assignment.data(),
243+
init_sol_assignment.data(),
244+
init_sol_assignment.size(),
245+
sol.handle_ptr->get_stream());
242246
relaxed_lp_settings_t lp_settings;
243247
lp_settings.time_limit = std::min(60., timer.remaining_time() / 2);
244248
lp_settings.tolerance = problem_ptr->tolerances.absolute_tolerance;
@@ -249,11 +253,15 @@ void diversity_manager_t<i_t, f_t>::add_user_given_solutions(
249253
problem_ptr->integer_indices,
250254
lp_settings,
251255
static_cast<bound_presolve_t<i_t, f_t>*>(nullptr));
252-
raft::copy(sol.assignment.data(),
253-
init_sol_assignment.data(),
254-
init_sol_assignment.size(),
255-
sol.handle_ptr->get_stream());
256256
bool is_feasible = sol.compute_feasibility();
257+
if (!is_feasible) {
258+
raft::copy(sol.assignment.data(),
259+
init_sol_assignment.data(),
260+
init_sol_assignment.size(),
261+
sol.handle_ptr->get_stream());
262+
is_feasible = sol.compute_feasibility();
263+
}
264+
257265
cuopt_func_call(sol.test_variable_bounds(true));
258266
CUOPT_LOG_DEBUG("Adding initial solution success! feas %d objective %f excess %f",
259267
is_feasible,
@@ -263,9 +271,8 @@ void diversity_manager_t<i_t, f_t>::add_user_given_solutions(
263271
initial_sol_vector.emplace_back(std::move(sol));
264272
} else {
265273
CUOPT_LOG_ERROR(
266-
"Error cannot add the provided initial solution! \
267-
Assignment size %lu \
268-
initial solution size %lu",
274+
"Error cannot add the provided initial solution! Assignment size %lu initial solution size "
275+
"%lu",
269276
sol.assignment.size(),
270277
init_sol_assignment.size());
271278
}

0 commit comments

Comments
 (0)