-
Notifications
You must be signed in to change notification settings - Fork 161
Initial symmetry detection 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
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), | ||
|
|
@@ -1388,6 +1391,163 @@ dual::status_t branch_and_bound_t<i_t, f_t>::solve_node_lp( | |
| worker->leaf_edge_norms = edge_norms_; | ||
|
|
||
| if (feasible) { | ||
|
|
||
|
|
||
| // Perform orbital fixing | ||
| if (symmetry_ != nullptr) { | ||
| // First get the set of variables that have been branched down and branched up on | ||
| std::vector<i_t> branched_zero; | ||
| std::vector<i_t> branched_one; | ||
| branched_zero.reserve(node_ptr->depth); | ||
| branched_one.reserve(node_ptr->depth); | ||
| mip_node_t<i_t, f_t>* node = node_ptr; | ||
| while (node != nullptr && node->branch_var >= 0) { | ||
| if (node->branch_var_upper == 0.0) { | ||
| branched_zero.push_back(node->branch_var); | ||
| symmetry_->marked_b0[node->branch_var] = 1; | ||
| } else if (node->branch_var_lower == 1.0) { | ||
| branched_one.push_back(node->branch_var); | ||
| symmetry_->marked_b1[node->branch_var] = 1; | ||
| } else { | ||
| assert(false); // Unexpected non-binary variable. Only binaries supported in symmetry handling. | ||
| } | ||
| node = node->parent; | ||
| } | ||
|
|
||
| { | ||
| for (i_t j = 0; j < symmetry_->num_original_vars; j++) { | ||
| if (var_types_[j] == variable_type_t::CONTINUOUS) continue; | ||
| if (symmetry_->marked_b1[j] == 0 && worker->leaf_problem.lower[j] == 1.0) { | ||
| symmetry_->f1.push_back(j); | ||
| symmetry_->marked_f1[j] = 1; | ||
| } | ||
| if (symmetry_->marked_b0[j] == 0 && worker->leaf_problem.upper[j] == 0.0) { | ||
| symmetry_->f0.push_back(j); | ||
| symmetry_->marked_f0[j] = 1; | ||
| } | ||
| } | ||
|
|
||
| // Compute Stab(G, B1) and its orbits | ||
| std::vector<i_t> new_base; | ||
| new_base.reserve(symmetry_->num_original_vars); | ||
| for (i_t j: branched_one) { | ||
| new_base.push_back(j); | ||
| symmetry_->marked_variables[j] = 1; | ||
| } | ||
| for (i_t j = 0; j < symmetry_->num_original_vars; j++) { | ||
| if (symmetry_->marked_variables[j] == 0) { | ||
| new_base.push_back(j); | ||
| } | ||
| } | ||
| for (i_t j: branched_one) { | ||
| symmetry_->marked_variables[j] = 0; | ||
| } | ||
|
|
||
| symmetry_->schreier->set_base(new_base); | ||
|
|
||
| dejavu::groups::orbit orb; | ||
| orb.initialize(symmetry_->domain_size); | ||
| symmetry_->schreier->get_stabilizer_orbit(static_cast<int>(branched_one.size()), orb); | ||
|
|
||
| for (i_t v : branched_one) { | ||
| symmetry_->orbit_has_b1[orb.find_orbit(v)] = 1; | ||
| } | ||
|
|
||
| for (i_t v : branched_zero) { | ||
| symmetry_->orbit_has_b0[orb.find_orbit(v)] = 1; | ||
| } | ||
|
|
||
| for (i_t v: symmetry_->continuous_variables) { | ||
| symmetry_->orbit_has_continuous[orb.find_orbit(v)] = 1; | ||
| } | ||
|
|
||
| for (i_t v: symmetry_->f0) { | ||
| symmetry_->orbit_has_f0[orb.find_orbit(v)] = 1; | ||
| } | ||
|
|
||
| for (i_t v: symmetry_->f1) { | ||
| symmetry_->orbit_has_f1[orb.find_orbit(v)] = 1; | ||
| } | ||
|
|
||
| std::vector<i_t> fix_zero; // The set L0 of variables that can be fixed to 0 | ||
| std::vector<i_t> fix_one; // The set L1 of variables that can be fixed to 1 | ||
|
|
||
| for (i_t j = 0; j < symmetry_->num_original_vars; j++) { | ||
| i_t o = orb.find_orbit(j); | ||
| if (orb.orbit_size(o) < 2) continue; | ||
|
|
||
| if (symmetry_->orbit_has_b1[o] == 1 || symmetry_->orbit_has_continuous[o] == 1) { | ||
| // The orbit contains variables in B1 or continuous variables | ||
| // So we can't fix any variables in this orbit to 0 | ||
| continue; | ||
| } | ||
|
|
||
| if (symmetry_->orbit_has_b0[o] == 1 || symmetry_->orbit_has_f0[o] == 1) { | ||
| // The orbit of this variable contains variables in B0 or F0 | ||
| // So we can fix this variable to zero (provided its not already in B0 or F0) | ||
| if (symmetry_->marked_b0[j] == 0 && symmetry_->marked_f0[j] == 0) { | ||
| fix_zero.push_back(j); | ||
| } | ||
| } | ||
|
|
||
| if (symmetry_->orbit_has_f1[o] == 1) { | ||
| // The orbit of this variable contains variables in F1 | ||
| // So we can fix this variable to one (provided its not already in F1) | ||
| if (symmetry_->marked_f1[j] == 0) { | ||
| fix_one.push_back(j); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Restore the work arrays | ||
| for (i_t v: branched_one) { | ||
| symmetry_->orbit_has_b1[orb.find_orbit(v)] = 0; | ||
| symmetry_->marked_b1[v] = 0; | ||
| } | ||
|
|
||
| for (i_t v: branched_zero) { | ||
| symmetry_->orbit_has_b0[orb.find_orbit(v)] = 0; | ||
| symmetry_->marked_b0[v] = 0; | ||
| } | ||
|
|
||
| for (i_t v: symmetry_->continuous_variables) { | ||
| symmetry_->orbit_has_continuous[orb.find_orbit(v)] = 0; | ||
| } | ||
|
|
||
| for (i_t v: symmetry_->f0) { | ||
| symmetry_->orbit_has_f0[orb.find_orbit(v)] = 0; | ||
| symmetry_->marked_f0[v] = 0; | ||
| } | ||
|
|
||
| for (i_t v: symmetry_->f1) { | ||
| symmetry_->orbit_has_f1[orb.find_orbit(v)] = 0; | ||
| symmetry_->marked_f1[v] = 0; | ||
| } | ||
|
|
||
| symmetry_->f0.clear(); | ||
| symmetry_->f1.clear(); | ||
|
|
||
| settings_.log.printf( | ||
| "Orbital fixing at node %d: fixing %d variables to 0 and %d variables to 1\n", | ||
| node_ptr->node_id, | ||
| fix_zero.size(), | ||
| fix_one.size()); | ||
| // Finally fix the variables in L0 and L1 | ||
| for (i_t v: fix_zero) { | ||
| settings_.log.printf("Orbital fixing at node %d: fixing variable %d to 0\n", node_ptr->node_id, v); | ||
| worker->leaf_problem.lower[v] = 0.0; | ||
| worker->leaf_problem.upper[v] = 0.0; | ||
| } | ||
| for (i_t v: fix_one) { | ||
| settings_.log.printf("Orbital fixing at node %d: fixing variable %d to 1\n", node_ptr->node_id, v); | ||
| worker->leaf_problem.lower[v] = 1.0; | ||
| worker->leaf_problem.upper[v] = 1.0; | ||
| } | ||
|
Comment on lines
+1530
to
+1545
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. Excessive logging in hot path will degrade performance. This logging runs on every node solve where symmetry is enabled:
This will significantly slow down the solver and flood logs on large instances. ♻️ Suggested fix: use debug logging or remove per-variable logs- settings_.log.printf(
- "Orbital fixing at node %d: fixing %d variables to 0 and %d variables to 1\n",
- node_ptr->node_id,
- fix_zero.size(),
- fix_one.size());
+ if (fix_zero.size() > 0 || fix_one.size() > 0) {
+ settings_.log.debug(
+ "Orbital fixing at node %d: fixing %zu variables to 0 and %zu variables to 1\n",
+ node_ptr->node_id,
+ fix_zero.size(),
+ fix_one.size());
+ }
// Finally fix the variables in L0 and L1
for (i_t v: fix_zero) {
- settings_.log.printf("Orbital fixing at node %d: fixing variable %d to 0\n", node_ptr->node_id, v);
worker->leaf_problem.lower[v] = 0.0;
worker->leaf_problem.upper[v] = 0.0;
}
for (i_t v: fix_one) {
- settings_.log.printf("Orbital fixing at node %d: fixing variable %d to 1\n", node_ptr->node_id, v);
worker->leaf_problem.lower[v] = 1.0;
worker->leaf_problem.upper[v] = 1.0;
}🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
Comment on lines
+1396
to
+1547
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. Orbital fixing is currently skipped in deterministic B&B. The new logic only runs in 🤖 Prompt for AI AgentsMake orbital-fixing scratch state worker-local. This block mutates As per coding guidelines, "Ensure race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state." 🤖 Prompt for AI Agents
Contributor
Author
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. Yes, it needs to be thread local. I'm working on that. Slow your roll code rabbit :) 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.
🧠 Learnings used |
||
|
|
||
|
|
||
|
|
||
| i_t node_iter = 0; | ||
| f_t lp_start_time = tic(); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improper error handling for non-binary variables.
The
assert(false)on line 1412 is problematic:branched_zeroorbranched_oneIf non-binary integer variables are encountered, the code should handle this explicitly rather than relying on an assertion.
🛡️ Suggested fix: explicit skip with optional debug logging
if (node->branch_var_upper == 0.0) { branched_zero.push_back(node->branch_var); symmetry_->marked_b0[node->branch_var] = 1; } else if (node->branch_var_lower == 1.0) { branched_one.push_back(node->branch_var); symmetry_->marked_b1[node->branch_var] = 1; } else { - assert(false); // Unexpected non-binary variable. Only binaries supported in symmetry handling. + // Non-binary integer variable - skip for symmetry handling (only binaries supported) + // Continue walking up the tree without including this variable }📝 Committable suggestion