-
Notifications
You must be signed in to change notification settings - Fork 164
MIP symmetry detection and orbital fixing using dejavu #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
base: main
Are you sure you want to change the base?
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
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 |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| #include <branch_and_bound/branch_and_bound.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/presolve/conflict_graph/clique_table.cuh> | ||
|
|
@@ -248,11 +249,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), | ||
|
|
@@ -730,6 +733,20 @@ 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, | ||
|
|
@@ -1387,42 +1404,79 @@ 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); | ||
|
|
||
| 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); | ||
| // Perform orbital fixing | ||
| auto* of = worker->orbital_fixing.get(); | ||
| if (of != nullptr && !of->disabled()) { | ||
| i_t prev_fix = node_ptr->orbital_fix_zero.size() + node_ptr->orbital_fix_one.size(); | ||
| i_t conflicts = of->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 (of != nullptr) { | ||
| of->propagate_cumulative_fixings(node_ptr); | ||
| } | ||
|
|
||
| lp_status = convert_lp_status_to_dual_status(second_status); | ||
| 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) { | ||
| feasible = false; | ||
| stats.lexical_reduction_pruned_nodes++; | ||
| } | ||
| } | ||
|
|
||
| stats.total_lp_solve_time += toc(lp_start_time); | ||
| stats.total_lp_iters += node_iter; | ||
| 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); | ||
| } | ||
|
|
||
| stats.total_lp_solve_time += toc(lp_start_time); | ||
| stats.total_lp_iters += node_iter; | ||
| } | ||
| } | ||
|
|
||
| #ifdef LOG_NODE_SIMPLEX | ||
|
|
@@ -1438,6 +1492,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_; | ||
|
|
@@ -1563,6 +1618,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; | ||
|
|
||
|
|
@@ -1659,7 +1715,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 | ||
|
|
@@ -1801,7 +1857,7 @@ void branch_and_bound_t<i_t, f_t>::run_scheduler() | |
| template <typename i_t, typename f_t> | ||
| void branch_and_bound_t<i_t, f_t>::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
+1860
to
1862
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(); | ||
|
|
@@ -2524,6 +2580,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_); | ||
| } | ||
|
|
||
|
|
@@ -2593,6 +2650,17 @@ 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.