-
Notifications
You must be signed in to change notification settings - Fork 173
Detect MIP symmetry using dejavu; Perform orbital fixing and lexical reduction #1103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4fe3f94
ada114b
6425c25
f447d4c
89ce17f
fcd28d7
96010da
7ea524f
426582a
07d421b
24acd6f
659a7c4
7bd9569
2aba39f
d4658ed
6cdc089
1dfa784
9c41f40
18cf2df
c607017
98cef5a
a422f86
e054968
e787ec1
a7126e4
c890f2f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
| #include <branch_and_bound/diving_heuristics.hpp> | ||
| #include <branch_and_bound/mip_node.hpp> | ||
| #include <branch_and_bound/pseudo_costs.hpp> | ||
| #include <branch_and_bound/symmetry.hpp> | ||
|
|
||
| #include <cuts/cuts.hpp> | ||
| #include <mip_heuristics/feasibility_jump/cpu_fj_thread.cuh> | ||
|
|
@@ -247,11 +248,13 @@ branch_and_bound_t<i_t, f_t>::branch_and_bound_t( | |
| const simplex_solver_settings_t<i_t, f_t>& solver_settings, | ||
| f_t start_time, | ||
| const probing_implied_bound_t<i_t, f_t>& probing_implied_bound, | ||
| std::shared_ptr<detail::clique_table_t<i_t, f_t>> clique_table) | ||
| std::shared_ptr<detail::clique_table_t<i_t, f_t>> clique_table, | ||
| mip_symmetry_t<i_t, f_t>* symmetry) | ||
| : original_problem_(user_problem), | ||
| settings_(solver_settings), | ||
| probing_implied_bound_(probing_implied_bound), | ||
| clique_table_(std::move(clique_table)), | ||
| symmetry_(symmetry), | ||
| original_lp_(user_problem.handle_ptr, 1, 1, 1), | ||
| Arow_(1, 1, 0), | ||
| incumbent_(1), | ||
|
|
@@ -729,6 +732,22 @@ void branch_and_bound_t<i_t, f_t>::set_final_solution(mip_solution_t<i_t, f_t>& | |
| settings_.log.printf("Explored %d nodes in %.2fs.\n", | ||
| exploration_stats_.nodes_explored, | ||
| toc(exploration_stats_.start_time)); | ||
| if (exploration_stats_.orbital_fixing_nodes.load() > 0 || | ||
| exploration_stats_.orbital_conflict_nodes.load() > 0) { | ||
| settings_.log.printf( | ||
| "Orbital fixing applied at %lld nodes, %lld total variable fixings, " | ||
| "%lld nodes with conflicting orbits\n", | ||
| (long long)exploration_stats_.orbital_fixing_nodes.load(), | ||
| (long long)exploration_stats_.orbital_fixings_applied.load(), | ||
| (long long)exploration_stats_.orbital_conflict_nodes.load()); | ||
| } | ||
| if (exploration_stats_.lexical_reduction_nodes.load() > 0) { | ||
| settings_.log.printf( | ||
| "Lexical reduction applied at %lld nodes, %lld total variable fixings, %lld nodes pruned\n", | ||
| (long long)exploration_stats_.lexical_reduction_nodes.load(), | ||
| (long long)exploration_stats_.lexical_reduction_fixings_applied.load(), | ||
| (long long)exploration_stats_.lexical_reduction_pruned_nodes.load()); | ||
| } | ||
| settings_.log.printf("Absolute Gap %e Objective %.16e %s Bound %.16e\n", | ||
| gap, | ||
| obj, | ||
|
|
@@ -1300,6 +1319,48 @@ std::pair<node_status_t, branch_direction_t> branch_and_bound_t<i_t, f_t>::updat | |
| return update_tree_impl(node_ptr, search_tree, worker, lp_status, policy); | ||
| } | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| bool branch_and_bound_t<i_t, f_t>::apply_symmetry_reductions( | ||
| mip_node_t<i_t, f_t>* node_ptr, | ||
| branch_and_bound_worker_t<i_t, f_t>* worker, | ||
| branch_and_bound_stats_t<i_t, f_t>& stats) | ||
| { | ||
| // Perform orbital fixing | ||
| auto* orbital_fixing = worker->orbital_fixing.get(); | ||
| if (orbital_fixing != nullptr && !orbital_fixing->disabled()) { | ||
| i_t prev_fix = node_ptr->orbital_fix_zero.size() + node_ptr->orbital_fix_one.size(); | ||
| i_t conflicts = orbital_fixing->orbital_fixing(symmetry_, | ||
| settings_, | ||
| node_ptr, | ||
| worker->leaf_problem, | ||
| worker->start_lower, | ||
| worker->start_upper); | ||
| i_t new_fix = node_ptr->orbital_fix_zero.size() + node_ptr->orbital_fix_one.size(); | ||
| if (new_fix > prev_fix) { | ||
| ++stats.orbital_fixing_nodes; | ||
| stats.orbital_fixings_applied += (new_fix - prev_fix); | ||
| } | ||
| if (conflicts > 0) { ++stats.orbital_conflict_nodes; } | ||
| } else if (orbital_fixing != nullptr) { | ||
| orbital_fixing->propagate_cumulative_fixings(node_ptr); | ||
| } | ||
|
|
||
| if (settings_.symmetry == 2 && worker->lexical_reduction != nullptr) { | ||
| i_t lexical_reductions_info = | ||
| worker->lexical_reduction->lexical_reduce(symmetry_, node_ptr, worker->leaf_problem); | ||
| if (lexical_reductions_info > 0) { | ||
| stats.lexical_reduction_nodes++; | ||
| stats.lexical_reduction_fixings_applied += lexical_reductions_info; | ||
| } | ||
| if (lexical_reductions_info == -1) { | ||
| stats.lexical_reduction_pruned_nodes++; | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| dual::status_t branch_and_bound_t<i_t, f_t>::solve_node_lp( | ||
| mip_node_t<i_t, f_t>* node_ptr, | ||
|
|
@@ -1392,42 +1453,50 @@ dual::status_t branch_and_bound_t<i_t, f_t>::solve_node_lp( | |
| bool feasible = worker->set_lp_variable_bounds(node_ptr, settings_); | ||
| dual::status_t lp_status = dual::status_t::DUAL_UNBOUNDED; | ||
| worker->leaf_edge_norms = edge_norms_; | ||
| if (worker->recompute_bounds && worker->orbital_fixing && worker->search_strategy == BEST_FIRST) { | ||
| worker->orbital_fixing->reset(symmetry_, node_ptr); | ||
| } | ||
|
|
||
| if (feasible) { | ||
| i_t node_iter = 0; | ||
| f_t lp_start_time = tic(); | ||
|
|
||
| lp_status = dual_phase2_with_advanced_basis(2, | ||
| 0, | ||
| worker->recompute_basis, | ||
| lp_start_time, | ||
| worker->leaf_problem, | ||
| lp_settings, | ||
| leaf_vstatus, | ||
| worker->basis_factors, | ||
| worker->basic_list, | ||
| worker->nonbasic_list, | ||
| worker->leaf_solution, | ||
| node_iter, | ||
| worker->leaf_edge_norms); | ||
| feasible = apply_symmetry_reductions(node_ptr, worker, stats); | ||
|
|
||
| if (lp_status == dual::status_t::NUMERICAL) { | ||
| log.debug("Numerical issue node %d. Resolving from scratch.\n", node_ptr->node_id); | ||
| lp_status_t second_status = solve_linear_program_with_advanced_basis(worker->leaf_problem, | ||
| lp_start_time, | ||
| lp_settings, | ||
| worker->leaf_solution, | ||
| worker->basis_factors, | ||
| worker->basic_list, | ||
| worker->nonbasic_list, | ||
| leaf_vstatus, | ||
| worker->leaf_edge_norms); | ||
| if (feasible) { | ||
| i_t node_iter = 0; | ||
| f_t lp_start_time = tic(); | ||
|
|
||
| lp_status = dual_phase2_with_advanced_basis(2, | ||
| 0, | ||
| worker->recompute_basis, | ||
| lp_start_time, | ||
| worker->leaf_problem, | ||
| lp_settings, | ||
| leaf_vstatus, | ||
| worker->basis_factors, | ||
| worker->basic_list, | ||
| worker->nonbasic_list, | ||
| worker->leaf_solution, | ||
| node_iter, | ||
| worker->leaf_edge_norms); | ||
|
|
||
| if (lp_status == dual::status_t::NUMERICAL) { | ||
| log.debug("Numerical issue node %d. Resolving from scratch.\n", node_ptr->node_id); | ||
| lp_status_t second_status = | ||
| solve_linear_program_with_advanced_basis(worker->leaf_problem, | ||
| lp_start_time, | ||
| lp_settings, | ||
| worker->leaf_solution, | ||
| worker->basis_factors, | ||
| worker->basic_list, | ||
| worker->nonbasic_list, | ||
| leaf_vstatus, | ||
| worker->leaf_edge_norms); | ||
|
|
||
| lp_status = convert_lp_status_to_dual_status(second_status); | ||
| } | ||
|
|
||
| lp_status = convert_lp_status_to_dual_status(second_status); | ||
| stats.total_lp_solve_time += toc(lp_start_time); | ||
| stats.total_lp_iters += node_iter; | ||
| } | ||
|
|
||
| stats.total_lp_solve_time += toc(lp_start_time); | ||
| stats.total_lp_iters += node_iter; | ||
| } | ||
|
|
||
| #ifdef LOG_NODE_SIMPLEX | ||
|
|
@@ -1443,6 +1512,7 @@ void branch_and_bound_t<i_t, f_t>::plunge_with(branch_and_bound_worker_t<i_t, f_ | |
| stack.push_front(worker->start_node); | ||
| worker->recompute_basis = true; | ||
| worker->recompute_bounds = true; | ||
| worker->ensure_orbital_fixing(); | ||
|
|
||
| f_t lower_bound = get_lower_bound(); | ||
| f_t upper_bound = upper_bound_; | ||
|
|
@@ -1578,6 +1648,7 @@ template <typename i_t, typename f_t> | |
| void branch_and_bound_t<i_t, f_t>::dive_with(branch_and_bound_worker_t<i_t, f_t>* worker) | ||
| { | ||
| raft::common::nvtx::range scope("BB::diving_thread"); | ||
| if (worker->orbital_fixing) { worker->orbital_fixing->disable(); } | ||
| logger_t log; | ||
| log.log = false; | ||
|
|
||
|
|
@@ -1672,7 +1743,7 @@ void branch_and_bound_t<i_t, f_t>::run_scheduler() | |
| std::array<i_t, num_search_strategies> max_num_workers_per_type = | ||
| get_max_workers(num_workers, strategies); | ||
|
|
||
| worker_pool_.init(num_workers, original_lp_, Arow_, var_types_, settings_); | ||
| worker_pool_.init(num_workers, original_lp_, Arow_, var_types_, symmetry_, settings_); | ||
| active_workers_per_strategy_.fill(0); | ||
|
|
||
| #ifdef CUOPT_LOG_DEBUG | ||
|
|
@@ -1815,7 +1886,7 @@ template <typename i_t, typename f_t> | |
| void branch_and_bound_t<i_t, f_t>::single_threaded_solve() | ||
| { | ||
| raft::common::nvtx::range scope("BB::single_threaded_solve"); | ||
| worker_pool_.init(1, original_lp_, Arow_, var_types_, settings_); | ||
| worker_pool_.init(1, original_lp_, Arow_, var_types_, symmetry_, settings_); | ||
| branch_and_bound_worker_t<i_t, f_t>* worker = worker_pool_.get_idle_worker(); | ||
|
|
||
|
Comment on lines
+1889
to
1891
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Single-thread worker can remain active with stale lower bound after plunge.
🔧 Proposed fix worker->init_best_first(start_node.value(), original_lp_);
plunge_with(worker);
+ worker->is_active = false; // prevent stale worker bound from affecting global lower boundAlso applies to: 1860-1861 🤖 Prompt for AI Agents |
||
| f_t lower_bound = get_lower_bound(); | ||
|
|
@@ -2662,6 +2733,7 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut | |
| basic_list, | ||
| nonbasic_list, | ||
| basis_update, | ||
| symmetry_, | ||
| pc_); | ||
| } | ||
|
|
||
|
|
@@ -2731,6 +2803,18 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut | |
| node_queue_.push(search_tree_.root.get_down_child()); | ||
| node_queue_.push(search_tree_.root.get_up_child()); | ||
|
|
||
| if (symmetry_ != nullptr) { | ||
| i_t removed = | ||
| symmetry_->generators.template prune_by_bounds<f_t>(original_lp_.lower, original_lp_.upper); | ||
| if (removed > 0) { | ||
| symmetry_->num_generators = static_cast<int>(symmetry_->generators.num_generators()); | ||
| settings_.log.printf( | ||
| "Pruned %d generators invalidated by root-level bound tightening, %d remain\n", | ||
| removed, | ||
| symmetry_->num_generators); | ||
| } | ||
| } | ||
|
|
||
| settings_.log.printf("Exploring the B&B tree using %d threads\n\n", settings_.num_threads); | ||
| node_concurrent_halt_ = 0; | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.