From fc1ab19e71ed9245dc062f5a941b9854d32f43d4 Mon Sep 17 00:00:00 2001 From: "Nicolas L. Guidotti" Date: Tue, 2 Jun 2026 12:36:05 +0200 Subject: [PATCH 1/3] expose diving hyper parameters. fixed circular dependency between pseudo_costs, worker and diving_heuristics. Signed-off-by: Nicolas L. Guidotti --- .../cuopt/linear_programming/constants.h | 13 +++++ .../mip/diving_hyper_params.hpp | 54 ++++++++++++++++++ .../mip/solver_settings.hpp | 3 + cpp/src/branch_and_bound/branch_and_bound.cpp | 57 +++++++------------ .../branch_and_bound/diving_heuristics.cpp | 12 ++++ .../branch_and_bound/diving_heuristics.hpp | 37 ++++++++++++ cpp/src/branch_and_bound/pseudo_costs.hpp | 7 ++- cpp/src/branch_and_bound/worker.hpp | 24 ++------ .../dual_simplex/simplex_solver_settings.hpp | 27 +-------- cpp/src/math_optimization/solver_settings.cu | 12 ++++ cpp/src/mip_heuristics/solver.cu | 2 + 11 files changed, 165 insertions(+), 83 deletions(-) create mode 100644 cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp diff --git a/cpp/include/cuopt/linear_programming/constants.h b/cpp/include/cuopt/linear_programming/constants.h index 40e68eca65..a9eb652042 100644 --- a/cpp/include/cuopt/linear_programming/constants.h +++ b/cpp/include/cuopt/linear_programming/constants.h @@ -114,6 +114,19 @@ #define CUOPT_MIP_HYPER_HEURISTIC_RELATED_VARS_TIME_LIMIT \ "mip_hyper_heuristic_related_vars_time_limit" +/* @brief Diving heuristic toggles: -1 automatic, 0 disabled, 1 enabled */ +#define CUOPT_MIP_HYPER_DIVING_LINE_SEARCH "mip_hyper_diving_line_search" +#define CUOPT_MIP_HYPER_DIVING_PSEUDOCOST "mip_hyper_diving_pseudocost" +#define CUOPT_MIP_HYPER_DIVING_GUIDED "mip_hyper_diving_guided" +#define CUOPT_MIP_HYPER_DIVING_COEFFICIENT "mip_hyper_diving_coefficient" +/* @brief Diving heuristic limits */ +#define CUOPT_MIP_HYPER_DIVING_MIN_NODE_DEPTH "mip_hyper_diving_min_node_depth" +#define CUOPT_MIP_HYPER_DIVING_NODE_LIMIT "mip_hyper_diving_node_limit" +#define CUOPT_MIP_HYPER_DIVING_ITERATION_LIMIT_FACTOR "mip_hyper_diving_iteration_limit_factor" +#define CUOPT_MIP_HYPER_DIVING_BACKTRACK_LIMIT "mip_hyper_diving_backtrack_limit" +/* @brief Show per-strategy diving symbol in logs (true) instead of a generic 'D' */ +#define CUOPT_MIP_HYPER_DIVING_SHOW_DIVING_TYPE "mip_hyper_diving_show_diving_type" + /* @brief MIP determinism mode constants */ #define CUOPT_MODE_OPPORTUNISTIC 0 #define CUOPT_MODE_DETERMINISTIC 1 diff --git a/cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp b/cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp new file mode 100644 index 0000000000..a509d9ca03 --- /dev/null +++ b/cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp @@ -0,0 +1,54 @@ +/* clang-format off */ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* clang-format on */ + +#pragma once + +namespace cuopt::linear_programming { + +/** + * @brief Tuning knobs for the dual-simplex diving heuristics used in MIP B&B. + * + * Used directly as simplex_solver_settings_t::diving_settings and copied into + * branch_and_bound_settings.diving_settings before solve. These are registered + * in the unified parameter framework via solver_settings_t and can be loaded + * from a config file with load_parameters_from_file(). + */ +struct mip_diving_hyper_params_t { + // -1 automatic, 0 disabled, 1 enabled + int line_search_diving = -1; + int pseudocost_diving = -1; + int guided_diving = -1; + int coefficient_diving = -1; + + // The minimum depth to start diving from. + int min_node_depth = 10; + + // The maximum number of nodes when performing a dive. + int node_limit = 500; + + // The maximum number of dual simplex iteration allowed + // in a single dive. This set in terms of the total number of + // iterations in the best-first threads. + double iteration_limit_factor = 0.05; + + // The maximum backtracking allowed. + int backtrack_limit = 5; + + // If a given diving heuristic found a new incumbent, show the corresponding + // symbol in the first column of the log row. When false, every dive collapses + // to 'D'. Otherwise, + // B = best-first + // H = heuristics + // C = coefficient diving + // L = line-search diving + // P = pseudocost diving + // G = guided diving + // U = unknown + bool show_diving_type = false; +}; + +} // namespace cuopt::linear_programming diff --git a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp index 685b1360b8..9f9e64e7ba 100644 --- a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -164,6 +165,8 @@ class mip_solver_settings_t { mip_heuristics_hyper_params_t heuristic_params; + mip_diving_hyper_params_t diving_params; + private: std::vector mip_callbacks_; std::optional semi_continuous_original_num_variables_; diff --git a/cpp/src/branch_and_bound/branch_and_bound.cpp b/cpp/src/branch_and_bound/branch_and_bound.cpp index 0222ad6fe9..8283888442 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.cpp +++ b/cpp/src/branch_and_bound/branch_and_bound.cpp @@ -213,32 +213,6 @@ std::string user_mip_gap(const lp_problem_t& lp, f_t obj_value, f_t lo } } -#ifdef SHOW_DIVING_TYPE -inline char feasible_solution_symbol(search_strategy_t strategy) -{ - switch (strategy) { - case search_strategy_t::BEST_FIRST: return 'B'; - case search_strategy_t::COEFFICIENT_DIVING: return 'C'; - case search_strategy_t::LINE_SEARCH_DIVING: return 'L'; - case search_strategy_t::PSEUDOCOST_DIVING: return 'P'; - case search_strategy_t::GUIDED_DIVING: return 'G'; - default: return 'U'; - } -} -#else -inline char feasible_solution_symbol(search_strategy_t strategy) -{ - switch (strategy) { - case search_strategy_t::BEST_FIRST: return 'B'; - case search_strategy_t::COEFFICIENT_DIVING: return 'D'; - case search_strategy_t::LINE_SEARCH_DIVING: return 'D'; - case search_strategy_t::PSEUDOCOST_DIVING: return 'D'; - case search_strategy_t::GUIDED_DIVING: return 'D'; - default: return 'U'; - } -} -#endif - } // namespace template @@ -811,15 +785,20 @@ void branch_and_bound_t::add_feasible_solution(f_t leaf_objective, { bool send_solution = false; + const bool log_diving_type = settings_.diving_settings.show_diving_type; settings_.log.debug("%c found a feasible solution with obj=%.10e.\n", - feasible_solution_symbol(thread_type), + feasible_solution_symbol(thread_type, log_diving_type), compute_user_objective(original_lp_, leaf_objective)); mutex_upper_.lock(); if (improves_incumbent(leaf_objective)) { incumbent_.set_incumbent_solution(leaf_objective, leaf_solution); upper_bound_ = std::min(upper_bound_.load(), leaf_objective); - report(feasible_solution_symbol(thread_type), leaf_objective, get_lower_bound(), leaf_depth, 0); + report(feasible_solution_symbol(thread_type, log_diving_type), + leaf_objective, + get_lower_bound(), + leaf_depth, + 0); send_solution = true; } @@ -1761,22 +1740,24 @@ void branch_and_bound_t::best_first_search_with(bfs_worker_t settings_.bnb_steal_chance >= 0 ? settings_.bnb_steal_chance : MIP_DEFAULT_STEAL_CHANCE; node_queue_t& node_queue = worker->node_queue; - worker->calculate_num_diving_workers(bfs_worker_pool_.size(), - diving_worker_pool_.size(), - has_solver_space_incumbent(), - settings_.diving_settings); + mip_diving_hyper_params_t diving_settings = settings_.diving_settings; + if (diving_settings.guided_diving != 0 && !has_solver_space_incumbent()) { + diving_settings.guided_diving = 0; + } + + worker->calculate_num_diving_workers( + bfs_worker_pool_.size(), diving_worker_pool_.size(), diving_settings); while (solver_status_ == mip_status_t::UNSET && abs_gap > settings_.absolute_mip_gap_tol && rel_gap > settings_.relative_mip_gap_tol && node_queue.best_first_queue_size() > 0) { // If the guided diving was disabled previously due to the lack of an incumbent solution, // re-enable as soon as a new incumbent is found. if (diving_worker_pool_.size() > 0 && settings_.diving_settings.guided_diving != 0 && - worker->max_diving_workers[GUIDED_DIVING] == 0) { + diving_settings.guided_diving == 0) { if (has_solver_space_incumbent()) { - worker->calculate_num_diving_workers(bfs_worker_pool_.size(), - diving_worker_pool_.size(), - has_solver_space_incumbent(), - settings_.diving_settings); + diving_settings.guided_diving = 1; + worker->calculate_num_diving_workers( + bfs_worker_pool_.size(), diving_worker_pool_.size(), diving_settings); } } @@ -3547,7 +3528,7 @@ void branch_and_bound_t::deterministic_process_worker_solutions( i_t nodes_unexplored = exploration_stats_.nodes_unexplored.load(); search_strategy_t worker_type = get_worker_type(pool, sol->worker_id); - report(feasible_solution_symbol(worker_type), + report(feasible_solution_symbol(worker_type, settings_.diving_settings.show_diving_type), sol->objective, deterministic_lower, sol->depth, diff --git a/cpp/src/branch_and_bound/diving_heuristics.cpp b/cpp/src/branch_and_bound/diving_heuristics.cpp index 4989e48c6a..46a6816795 100644 --- a/cpp/src/branch_and_bound/diving_heuristics.cpp +++ b/cpp/src/branch_and_bound/diving_heuristics.cpp @@ -126,6 +126,12 @@ branch_variable_t pseudocost_diving(pseudo_costs_t& pc, round_dir = branch_direction_t::DOWN; } + log.debug("Pseudocost diving: selected var %d with val = %e, round dir = %d and score = %e\n", + branch_var, + solution[branch_var], + round_dir, + max_score); + return {branch_var, round_dir}; } @@ -168,6 +174,12 @@ branch_variable_t guided_diving(pseudo_costs_t& pc, } } + log.debug("Guided diving: selected var %d with val = %e, round dir = %d and score = %e\n", + branch_var, + solution[branch_var], + round_dir, + max_score); + return {branch_var, round_dir}; } diff --git a/cpp/src/branch_and_bound/diving_heuristics.hpp b/cpp/src/branch_and_bound/diving_heuristics.hpp index dfeabe3a5f..fe7b296dff 100644 --- a/cpp/src/branch_and_bound/diving_heuristics.hpp +++ b/cpp/src/branch_and_bound/diving_heuristics.hpp @@ -16,6 +16,43 @@ namespace cuopt::linear_programming::dual_simplex { +// When `log_diving_type` is true, each diving strategy gets its own letter; +// otherwise every dive collapses to 'D'. +inline char feasible_solution_symbol(search_strategy_t strategy, bool log_diving_type) +{ + if (strategy == BEST_FIRST) return 'B'; + if (!log_diving_type) { + switch (strategy) { + case COEFFICIENT_DIVING: + case LINE_SEARCH_DIVING: + case PSEUDOCOST_DIVING: + case GUIDED_DIVING: return 'D'; + default: return 'U'; + } + } + switch (strategy) { + case COEFFICIENT_DIVING: return 'C'; + case LINE_SEARCH_DIVING: return 'L'; + case PSEUDOCOST_DIVING: return 'P'; + case GUIDED_DIVING: return 'G'; + default: return 'U'; + } +} + +inline bool is_search_strategy_enabled(search_strategy_t strategy, + const mip_diving_hyper_params_t& settings) +{ + switch (strategy) { + case BEST_FIRST: return true; + case PSEUDOCOST_DIVING: return settings.pseudocost_diving != 0; + case LINE_SEARCH_DIVING: return settings.line_search_diving != 0; + case GUIDED_DIVING: return settings.guided_diving != 0; + case COEFFICIENT_DIVING: return settings.coefficient_diving != 0; + } + + return false; +} + template branch_variable_t line_search_diving(const std::vector& fractional, const std::vector& solution, diff --git a/cpp/src/branch_and_bound/pseudo_costs.hpp b/cpp/src/branch_and_bound/pseudo_costs.hpp index a370a00de8..6fd7dc5bb6 100644 --- a/cpp/src/branch_and_bound/pseudo_costs.hpp +++ b/cpp/src/branch_and_bound/pseudo_costs.hpp @@ -9,7 +9,6 @@ #include #include -#include #include #include @@ -30,6 +29,12 @@ namespace cuopt::linear_programming::dual_simplex { template struct mip_symmetry_t; +template +struct branch_and_bound_worker_t; + +template +struct branch_and_bound_stats_t; + template struct reliability_branching_settings_t { // Lower bound for the maximum number of LP iterations for a single trial branching diff --git a/cpp/src/branch_and_bound/worker.hpp b/cpp/src/branch_and_bound/worker.hpp index 29a3164dfc..0e0761e5e6 100644 --- a/cpp/src/branch_and_bound/worker.hpp +++ b/cpp/src/branch_and_bound/worker.hpp @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -42,22 +43,6 @@ struct branch_and_bound_stats_t { omp_atomic_t lexical_reduction_pruned_nodes = 0; }; -template -bool is_search_strategy_enabled(search_strategy_t strategy, - bool has_incumbent, - diving_heuristics_settings_t settings) -{ - switch (strategy) { - case BEST_FIRST: return true; - case PSEUDOCOST_DIVING: return settings.pseudocost_diving != 0; - case LINE_SEARCH_DIVING: return settings.line_search_diving != 0; - case GUIDED_DIVING: return settings.guided_diving != 0 && has_incumbent; - case COEFFICIENT_DIVING: return settings.coefficient_diving != 0; - } - - return false; -} - template class branch_and_bound_worker_t { public: @@ -191,12 +176,11 @@ class bfs_worker_t : public branch_and_bound_worker_t { // of workers allows the solver to be more deterministic. void calculate_num_diving_workers(i_t num_bfs_workers, i_t total_diving_workers, - bool has_incumbent, - const diving_heuristics_settings_t& settings) + const mip_diving_hyper_params_t& settings) { i_t num_active = 0; for (i_t i = 1; i < num_search_strategies; ++i) { - num_active += is_search_strategy_enabled(search_strategies[i], has_incumbent, settings); + num_active += is_search_strategy_enabled(search_strategies[i], settings); } total_max_diving_workers = 0; @@ -204,7 +188,7 @@ class bfs_worker_t : public branch_and_bound_worker_t { if (num_active == 0) { return; } for (size_t i = 1, k = 0; i < num_search_strategies; ++i) { - if (is_search_strategy_enabled(search_strategies[i], has_incumbent, settings)) { + if (is_search_strategy_enabled(search_strategies[i], settings)) { // Calculate the number of workers for a given diving heuristic auto [type_start, type_end] = calculate_index_range(k, total_diving_workers, num_active); i_t workers_per_type = type_end - type_start; diff --git a/cpp/src/dual_simplex/simplex_solver_settings.hpp b/cpp/src/dual_simplex/simplex_solver_settings.hpp index 27eac7f985..3bbd85daf3 100644 --- a/cpp/src/dual_simplex/simplex_solver_settings.hpp +++ b/cpp/src/dual_simplex/simplex_solver_settings.hpp @@ -7,6 +7,8 @@ #pragma once +#include + #include #include @@ -20,29 +22,6 @@ namespace cuopt::linear_programming::dual_simplex { -template -struct diving_heuristics_settings_t { - // -1 automatic, 0 disabled, 1 enabled - i_t line_search_diving = -1; - i_t pseudocost_diving = -1; - i_t guided_diving = -1; - i_t coefficient_diving = -1; - - // The minimum depth to start diving from. - i_t min_node_depth = 10; - - // The maximum number of nodes when performing a dive. - i_t node_limit = 500; - - // The maximum number of dual simplex iteration allowed - // in a single dive. This set in terms of the total number of - // iterations in the best-first threads. - f_t iteration_limit_factor = 0.05; - - // The maximum backtracking allowed. - i_t backtrack_limit = 5; -}; - template struct simplex_solver_settings_t { public: @@ -214,7 +193,7 @@ struct simplex_solver_settings_t { // >1 - set as the iteration limit in dual simplex i_t strong_branching_simplex_iteration_limit; - diving_heuristics_settings_t diving_settings; // Settings for the diving heuristics + mip_diving_hyper_params_t diving_settings; // Settings for the diving heuristics // In B&B, indicate the chance in which a worker can steal a node from another worker. // -1 - automatic (0.05) diff --git a/cpp/src/math_optimization/solver_settings.cu b/cpp/src/math_optimization/solver_settings.cu index 2a7d942da5..30e04fe3a9 100644 --- a/cpp/src/math_optimization/solver_settings.cu +++ b/cpp/src/math_optimization/solver_settings.cu @@ -115,6 +115,8 @@ solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings {CUOPT_MIP_HYPER_HEURISTIC_RELAXED_LP_TIME_LIMIT, &mip_settings.heuristic_params.relaxed_lp_time_limit, f_t(1e-9), std::numeric_limits::infinity(), f_t(1.0), "base relaxed LP time cap in heuristics"}, {CUOPT_MIP_HYPER_HEURISTIC_RELATED_VARS_TIME_LIMIT, &mip_settings.heuristic_params.related_vars_time_limit, f_t(1e-9), std::numeric_limits::infinity(), f_t(30.0), "time for related-variable structure build"}, {CUOPT_MIP_SEMICONTINUOUS_BIG_M, &mip_settings.semi_continuous_big_m, f_t(1.0), std::numeric_limits::infinity(), f_t(1e10), "big-M value for semi-continuous variables with no finite upper bound"}, + // Diving heuristic hyper-parameters (hidden from default --help: name contains "hyper_") + {CUOPT_MIP_HYPER_DIVING_ITERATION_LIMIT_FACTOR, &mip_settings.diving_params.iteration_limit_factor, f_t(0.0), f_t(1.0), f_t(0.05), "fraction of best-first iterations allowed per dive"}, }; // Int parameters @@ -161,6 +163,14 @@ solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings {CUOPT_MIP_HYPER_HEURISTIC_N_OF_MINIMUMS_FOR_EXIT, &mip_settings.heuristic_params.n_of_minimums_for_exit, 1, std::numeric_limits::max(), 7000, "FJ baseline local-minima exit threshold"}, {CUOPT_MIP_HYPER_HEURISTIC_ENABLED_RECOMBINERS, &mip_settings.heuristic_params.enabled_recombiners, 0, 15, 15, "bitmask: 1=BP 2=FP 4=LS 8=SubMIP"}, {CUOPT_MIP_HYPER_HEURISTIC_CYCLE_DETECTION_LENGTH, &mip_settings.heuristic_params.cycle_detection_length, 1, std::numeric_limits::max(), 30, "FP assignment cycle ring buffer length"}, + // Diving heuristic hyper-parameters (hidden from default --help: name contains "hyper_") + {CUOPT_MIP_HYPER_DIVING_LINE_SEARCH, &mip_settings.diving_params.line_search_diving, -1, 1, -1, "line-search diving toggle: -1 automatic, 0 disabled, 1 enabled"}, + {CUOPT_MIP_HYPER_DIVING_PSEUDOCOST, &mip_settings.diving_params.pseudocost_diving, -1, 1, -1, "pseudocost diving toggle: -1 automatic, 0 disabled, 1 enabled"}, + {CUOPT_MIP_HYPER_DIVING_GUIDED, &mip_settings.diving_params.guided_diving, -1, 1, -1, "guided diving toggle: -1 automatic, 0 disabled, 1 enabled"}, + {CUOPT_MIP_HYPER_DIVING_COEFFICIENT, &mip_settings.diving_params.coefficient_diving, -1, 1, -1, "coefficient diving toggle: -1 automatic, 0 disabled, 1 enabled"}, + {CUOPT_MIP_HYPER_DIVING_MIN_NODE_DEPTH, &mip_settings.diving_params.min_node_depth, 0, std::numeric_limits::max(), 10, "minimum depth at which to start diving"}, + {CUOPT_MIP_HYPER_DIVING_NODE_LIMIT, &mip_settings.diving_params.node_limit, 0, std::numeric_limits::max(), 500, "maximum nodes explored per dive"}, + {CUOPT_MIP_HYPER_DIVING_BACKTRACK_LIMIT, &mip_settings.diving_params.backtrack_limit, 0, std::numeric_limits::max(), 5, "maximum backtracking allowed per dive"}, }; // Bool parameters @@ -179,6 +189,8 @@ solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings {CUOPT_DUAL_POSTSOLVE, &pdlp_settings.dual_postsolve, true}, {CUOPT_BARRIER_ITERATIVE_REFINEMENT, &pdlp_settings.barrier_iterative_refinement, true}, {CUOPT_MIP_PROBING, &mip_settings.probing, true}, + // Diving heuristic hyper-parameters (hidden from default --help: name contains "hyper_") + {CUOPT_MIP_HYPER_DIVING_SHOW_DIVING_TYPE, &mip_settings.diving_params.show_diving_type, false, "log diving heuristic type when it finds a new incumbent"}, }; // String parameters string_parameters = { diff --git a/cpp/src/mip_heuristics/solver.cu b/cpp/src/mip_heuristics/solver.cu index c25ade0c05..187f774c37 100644 --- a/cpp/src/mip_heuristics/solver.cu +++ b/cpp/src/mip_heuristics/solver.cu @@ -393,6 +393,8 @@ solution_t mip_solver_t::run_solver() : context.settings.reduced_cost_strengthening; branch_and_bound_settings.symmetry = context.settings.symmetry; + branch_and_bound_settings.diving_settings = context.settings.diving_params; + // Set the branch and bound -> primal heuristics callback branch_and_bound_settings.solution_callback = std::bind(&branch_and_bound_solution_helper_t::solution_callback, From 2fedec890a3bec7fbe8f54a33b0be0ada0a46f15 Mon Sep 17 00:00:00 2001 From: "Nicolas L. Guidotti" Date: Tue, 2 Jun 2026 14:11:40 +0200 Subject: [PATCH 2/3] transform hyper parameters into templated objects. addressed other coderabbit comments. Signed-off-by: Nicolas L. Guidotti --- .../cuopt/linear_programming/constants.h | 2 +- .../mip/diving_hyper_params.hpp | 11 +++--- .../mip/heuristics_hyper_params.hpp | 35 ++++++++++--------- .../mip/solver_settings.hpp | 4 +-- cpp/src/branch_and_bound/branch_and_bound.cpp | 6 ++-- .../branch_and_bound/diving_heuristics.hpp | 7 ++-- cpp/src/branch_and_bound/worker.hpp | 2 +- .../dual_simplex/simplex_solver_settings.hpp | 2 +- cpp/src/math_optimization/solver_settings.cu | 2 +- cpp/tests/mip/heuristics_hyper_params_test.cu | 2 +- 10 files changed, 39 insertions(+), 34 deletions(-) diff --git a/cpp/include/cuopt/linear_programming/constants.h b/cpp/include/cuopt/linear_programming/constants.h index a9eb652042..ae02ee00c7 100644 --- a/cpp/include/cuopt/linear_programming/constants.h +++ b/cpp/include/cuopt/linear_programming/constants.h @@ -125,7 +125,7 @@ #define CUOPT_MIP_HYPER_DIVING_ITERATION_LIMIT_FACTOR "mip_hyper_diving_iteration_limit_factor" #define CUOPT_MIP_HYPER_DIVING_BACKTRACK_LIMIT "mip_hyper_diving_backtrack_limit" /* @brief Show per-strategy diving symbol in logs (true) instead of a generic 'D' */ -#define CUOPT_MIP_HYPER_DIVING_SHOW_DIVING_TYPE "mip_hyper_diving_show_diving_type" +#define CUOPT_MIP_HYPER_DIVING_SHOW_TYPE "mip_hyper_diving_show_type" /* @brief MIP determinism mode constants */ #define CUOPT_MODE_OPPORTUNISTIC 0 diff --git a/cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp b/cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp index a509d9ca03..bd9e316104 100644 --- a/cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp +++ b/cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp @@ -17,6 +17,7 @@ namespace cuopt::linear_programming { * in the unified parameter framework via solver_settings_t and can be loaded * from a config file with load_parameters_from_file(). */ +template struct mip_diving_hyper_params_t { // -1 automatic, 0 disabled, 1 enabled int line_search_diving = -1; @@ -25,18 +26,18 @@ struct mip_diving_hyper_params_t { int coefficient_diving = -1; // The minimum depth to start diving from. - int min_node_depth = 10; + i_t min_node_depth = 10; // The maximum number of nodes when performing a dive. - int node_limit = 500; + i_t node_limit = 500; // The maximum number of dual simplex iteration allowed // in a single dive. This set in terms of the total number of // iterations in the best-first threads. - double iteration_limit_factor = 0.05; + f_t iteration_limit_factor = 0.05; // The maximum backtracking allowed. - int backtrack_limit = 5; + i_t backtrack_limit = 5; // If a given diving heuristic found a new incumbent, show the corresponding // symbol in the first column of the log row. When false, every dive collapses @@ -48,7 +49,7 @@ struct mip_diving_hyper_params_t { // P = pseudocost diving // G = guided diving // U = unknown - bool show_diving_type = false; + bool show_type = false; }; } // namespace cuopt::linear_programming diff --git a/cpp/include/cuopt/linear_programming/mip/heuristics_hyper_params.hpp b/cpp/include/cuopt/linear_programming/mip/heuristics_hyper_params.hpp index c0b644544a..71bbbe169a 100644 --- a/cpp/include/cuopt/linear_programming/mip/heuristics_hyper_params.hpp +++ b/cpp/include/cuopt/linear_programming/mip/heuristics_hyper_params.hpp @@ -17,24 +17,25 @@ namespace cuopt::linear_programming { * These are registered in the unified parameter framework via solver_settings_t * and can be loaded from a config file with load_parameters_from_file(). */ +template struct mip_heuristics_hyper_params_t { - int population_size = 32; // max solutions in pool - int num_cpufj_threads = 8; // parallel CPU FJ climbers - double presolve_time_ratio = 0.1; // fraction of total time for presolve - double presolve_max_time = 60.0; // hard cap on presolve seconds - double root_lp_time_ratio = 0.1; // fraction of total time for root LP - double root_lp_max_time = 15.0; // hard cap on root LP seconds - double rins_time_limit = 3.0; // per-call RINS sub-MIP time - double rins_max_time_limit = 20.0; // ceiling for RINS adaptive time budget - double rins_fix_rate = 0.5; // RINS variable fix rate - int stagnation_trigger = 3; // FP loops w/o improvement before recombination - int max_iterations_without_improvement = 8; // diversity step depth after stagnation - double initial_infeasibility_weight = 1000.0; // constraint violation penalty seed - int n_of_minimums_for_exit = 7000; // FJ baseline local-minima exit threshold - int enabled_recombiners = 15; // bitmask: 1=BP 2=FP 4=LS 8=SubMIP - int cycle_detection_length = 30; // FP assignment cycle ring buffer - double relaxed_lp_time_limit = 1.0; // base relaxed LP time cap in heuristics - double related_vars_time_limit = 30.0; // time for related-variable structure build + i_t population_size = 32; // max solutions in pool + i_t num_cpufj_threads = 8; // parallel CPU FJ climbers + f_t presolve_time_ratio = 0.1; // fraction of total time for presolve + f_t presolve_max_time = 60.0; // hard cap on presolve seconds + f_t root_lp_time_ratio = 0.1; // fraction of total time for root LP + f_t root_lp_max_time = 15.0; // hard cap on root LP seconds + f_t rins_time_limit = 3.0; // per-call RINS sub-MIP time + f_t rins_max_time_limit = 20.0; // ceiling for RINS adaptive time budget + f_t rins_fix_rate = 0.5; // RINS variable fix rate + i_t stagnation_trigger = 3; // FP loops w/o improvement before recombination + i_t max_iterations_without_improvement = 8; // diversity step depth after stagnation + f_t initial_infeasibility_weight = 1000.0; // constraint violation penalty seed + i_t n_of_minimums_for_exit = 7000; // FJ baseline local-minima exit threshold + i_t enabled_recombiners = 15; // bitmask: 1=BP 2=FP 4=LS 8=SubMIP + i_t cycle_detection_length = 30; // FP assignment cycle ring buffer + f_t relaxed_lp_time_limit = 1.0; // base relaxed LP time cap in heuristics + f_t related_vars_time_limit = 30.0; // time for related-variable structure build }; } // namespace cuopt::linear_programming diff --git a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp index 9f9e64e7ba..1038c29057 100644 --- a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp @@ -163,9 +163,9 @@ class mip_solver_settings_t { // TODO check with Akif and Alice pdlp_hyper_params::pdlp_hyper_params_t hyper_params; - mip_heuristics_hyper_params_t heuristic_params; + mip_heuristics_hyper_params_t heuristic_params; - mip_diving_hyper_params_t diving_params; + mip_diving_hyper_params_t diving_params; private: std::vector mip_callbacks_; diff --git a/cpp/src/branch_and_bound/branch_and_bound.cpp b/cpp/src/branch_and_bound/branch_and_bound.cpp index 8283888442..a568aacd47 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.cpp +++ b/cpp/src/branch_and_bound/branch_and_bound.cpp @@ -785,7 +785,7 @@ void branch_and_bound_t::add_feasible_solution(f_t leaf_objective, { bool send_solution = false; - const bool log_diving_type = settings_.diving_settings.show_diving_type; + const bool log_diving_type = settings_.diving_settings.show_type; settings_.log.debug("%c found a feasible solution with obj=%.10e.\n", feasible_solution_symbol(thread_type, log_diving_type), compute_user_objective(original_lp_, leaf_objective)); @@ -1740,7 +1740,7 @@ void branch_and_bound_t::best_first_search_with(bfs_worker_t settings_.bnb_steal_chance >= 0 ? settings_.bnb_steal_chance : MIP_DEFAULT_STEAL_CHANCE; node_queue_t& node_queue = worker->node_queue; - mip_diving_hyper_params_t diving_settings = settings_.diving_settings; + mip_diving_hyper_params_t diving_settings = settings_.diving_settings; if (diving_settings.guided_diving != 0 && !has_solver_space_incumbent()) { diving_settings.guided_diving = 0; } @@ -3528,7 +3528,7 @@ void branch_and_bound_t::deterministic_process_worker_solutions( i_t nodes_unexplored = exploration_stats_.nodes_unexplored.load(); search_strategy_t worker_type = get_worker_type(pool, sol->worker_id); - report(feasible_solution_symbol(worker_type, settings_.diving_settings.show_diving_type), + report(feasible_solution_symbol(worker_type, settings_.diving_settings.show_type), sol->objective, deterministic_lower, sol->depth, diff --git a/cpp/src/branch_and_bound/diving_heuristics.hpp b/cpp/src/branch_and_bound/diving_heuristics.hpp index fe7b296dff..f22434b5cd 100644 --- a/cpp/src/branch_and_bound/diving_heuristics.hpp +++ b/cpp/src/branch_and_bound/diving_heuristics.hpp @@ -7,6 +7,8 @@ #pragma once +#include + #include #include @@ -39,8 +41,9 @@ inline char feasible_solution_symbol(search_strategy_t strategy, bool log_diving } } -inline bool is_search_strategy_enabled(search_strategy_t strategy, - const mip_diving_hyper_params_t& settings) +template +bool is_search_strategy_enabled(search_strategy_t strategy, + const mip_diving_hyper_params_t& settings) { switch (strategy) { case BEST_FIRST: return true; diff --git a/cpp/src/branch_and_bound/worker.hpp b/cpp/src/branch_and_bound/worker.hpp index 0e0761e5e6..62bd0cde6b 100644 --- a/cpp/src/branch_and_bound/worker.hpp +++ b/cpp/src/branch_and_bound/worker.hpp @@ -176,7 +176,7 @@ class bfs_worker_t : public branch_and_bound_worker_t { // of workers allows the solver to be more deterministic. void calculate_num_diving_workers(i_t num_bfs_workers, i_t total_diving_workers, - const mip_diving_hyper_params_t& settings) + const mip_diving_hyper_params_t& settings) { i_t num_active = 0; for (i_t i = 1; i < num_search_strategies; ++i) { diff --git a/cpp/src/dual_simplex/simplex_solver_settings.hpp b/cpp/src/dual_simplex/simplex_solver_settings.hpp index 3bbd85daf3..500af4286e 100644 --- a/cpp/src/dual_simplex/simplex_solver_settings.hpp +++ b/cpp/src/dual_simplex/simplex_solver_settings.hpp @@ -193,7 +193,7 @@ struct simplex_solver_settings_t { // >1 - set as the iteration limit in dual simplex i_t strong_branching_simplex_iteration_limit; - mip_diving_hyper_params_t diving_settings; // Settings for the diving heuristics + mip_diving_hyper_params_t diving_settings; // Settings for the diving heuristics // In B&B, indicate the chance in which a worker can steal a node from another worker. // -1 - automatic (0.05) diff --git a/cpp/src/math_optimization/solver_settings.cu b/cpp/src/math_optimization/solver_settings.cu index 30e04fe3a9..df51dd688e 100644 --- a/cpp/src/math_optimization/solver_settings.cu +++ b/cpp/src/math_optimization/solver_settings.cu @@ -190,7 +190,7 @@ solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings {CUOPT_BARRIER_ITERATIVE_REFINEMENT, &pdlp_settings.barrier_iterative_refinement, true}, {CUOPT_MIP_PROBING, &mip_settings.probing, true}, // Diving heuristic hyper-parameters (hidden from default --help: name contains "hyper_") - {CUOPT_MIP_HYPER_DIVING_SHOW_DIVING_TYPE, &mip_settings.diving_params.show_diving_type, false, "log diving heuristic type when it finds a new incumbent"}, + {CUOPT_MIP_HYPER_DIVING_SHOW_TYPE, &mip_settings.diving_params.show_type, false, "log diving heuristic type when it finds a new incumbent"}, }; // String parameters string_parameters = { diff --git a/cpp/tests/mip/heuristics_hyper_params_test.cu b/cpp/tests/mip/heuristics_hyper_params_test.cu index 50e463b1fe..24b15f8cc8 100644 --- a/cpp/tests/mip/heuristics_hyper_params_test.cu +++ b/cpp/tests/mip/heuristics_hyper_params_test.cu @@ -114,7 +114,7 @@ TEST_F(HeuristicsHyperParamsTest, PartialConfigKeepsDefaults) EXPECT_EQ(hp.population_size, 128); EXPECT_DOUBLE_EQ(hp.rins_fix_rate, 0.3); - mip_heuristics_hyper_params_t defaults; + mip_heuristics_hyper_params_t defaults; EXPECT_EQ(hp.num_cpufj_threads, defaults.num_cpufj_threads); EXPECT_DOUBLE_EQ(hp.presolve_time_ratio, defaults.presolve_time_ratio); EXPECT_EQ(hp.n_of_minimums_for_exit, defaults.n_of_minimums_for_exit); From 1da6200e7713ecd006bf66ec3118c9ca488fbfd6 Mon Sep 17 00:00:00 2001 From: "Nicolas L. Guidotti" Date: Tue, 2 Jun 2026 14:30:39 +0200 Subject: [PATCH 3/3] removed switch case Signed-off-by: Nicolas L. Guidotti --- .../linear_programming/mip/diving_hyper_params.hpp | 8 ++++---- cpp/src/branch_and_bound/diving_heuristics.hpp | 10 +--------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp b/cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp index bd9e316104..6ec0058334 100644 --- a/cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp +++ b/cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp @@ -20,10 +20,10 @@ namespace cuopt::linear_programming { template struct mip_diving_hyper_params_t { // -1 automatic, 0 disabled, 1 enabled - int line_search_diving = -1; - int pseudocost_diving = -1; - int guided_diving = -1; - int coefficient_diving = -1; + i_t line_search_diving = -1; + i_t pseudocost_diving = -1; + i_t guided_diving = -1; + i_t coefficient_diving = -1; // The minimum depth to start diving from. i_t min_node_depth = 10; diff --git a/cpp/src/branch_and_bound/diving_heuristics.hpp b/cpp/src/branch_and_bound/diving_heuristics.hpp index f22434b5cd..79a23f08d3 100644 --- a/cpp/src/branch_and_bound/diving_heuristics.hpp +++ b/cpp/src/branch_and_bound/diving_heuristics.hpp @@ -23,15 +23,7 @@ namespace cuopt::linear_programming::dual_simplex { inline char feasible_solution_symbol(search_strategy_t strategy, bool log_diving_type) { if (strategy == BEST_FIRST) return 'B'; - if (!log_diving_type) { - switch (strategy) { - case COEFFICIENT_DIVING: - case LINE_SEARCH_DIVING: - case PSEUDOCOST_DIVING: - case GUIDED_DIVING: return 'D'; - default: return 'U'; - } - } + if (!log_diving_type) { return 'D'; } switch (strategy) { case COEFFICIENT_DIVING: return 'C'; case LINE_SEARCH_DIVING: return 'L';