@@ -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+
32043225template <typename i_t , typename f_t >
32053226void 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
33463498template <typename i_t , typename f_t >
0 commit comments