Skip to content

Commit 6ec1fe8

Browse files
committed
simplified logic for pseudo cost (and its snapshot) for the regular and deterministic mode.
Signed-off-by: Nicolas Guidotti <224634272+nguidotti@users.noreply.github.com>
1 parent ba89839 commit 6ec1fe8

File tree

10 files changed

+446
-568
lines changed

10 files changed

+446
-568
lines changed

cpp/src/branch_and_bound/branch_and_bound.cpp

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,9 @@ struct deterministic_bfs_policy_t
10201020
const std::vector<i_t>& fractional,
10211021
const std::vector<f_t>& x) override
10221022
{
1023-
i_t var = this->worker.pc_snapshot.variable_selection(fractional, x);
1023+
logger_t log;
1024+
log.log = false;
1025+
i_t var = this->worker.pc_snapshot.variable_selection(fractional, x, log);
10241026
auto dir = martin_criteria(x[var], this->bnb.root_relax_soln_.x[var]);
10251027
return {var, dir};
10261028
}
@@ -1029,8 +1031,10 @@ struct deterministic_bfs_policy_t
10291031
const std::vector<i_t>& fractional,
10301032
const std::vector<f_t>& x) override
10311033
{
1034+
logger_t log;
1035+
log.log = false;
10321036
node->objective_estimate =
1033-
this->worker.pc_snapshot.obj_estimate(fractional, x, node->lower_bound);
1037+
this->worker.pc_snapshot.obj_estimate(fractional, x, node->lower_bound, log);
10341038
}
10351039

10361040
void on_node_completed(mip_node_t<i_t, f_t>* node,
@@ -1095,24 +1099,22 @@ struct deterministic_diving_policy_t
10951099
const std::vector<i_t>& fractional,
10961100
const std::vector<f_t>& x) override
10971101
{
1102+
logger_t log;
1103+
log.log = false;
1104+
10981105
switch (this->worker.diving_type) {
10991106
case search_strategy_t::PSEUDOCOST_DIVING:
1100-
return this->worker.variable_selection_from_snapshot(fractional, x);
1107+
return pseudocost_diving(
1108+
this->worker.pc_snapshot, fractional, x, *this->worker.root_solution, log);
11011109

11021110
case search_strategy_t::LINE_SEARCH_DIVING:
1103-
if (this->worker.root_solution) {
1104-
logger_t log;
1105-
log.log = false;
1106-
return line_search_diving<i_t, f_t>(fractional, x, *this->worker.root_solution, log);
1107-
}
1108-
return this->worker.variable_selection_from_snapshot(fractional, x);
1111+
return line_search_diving<i_t, f_t>(fractional, x, *this->worker.root_solution, log);
11091112

11101113
case search_strategy_t::GUIDED_DIVING:
1111-
return this->worker.guided_variable_selection(fractional, x);
1114+
return guided_diving(
1115+
this->worker.pc_snapshot, fractional, x, this->worker.incumbent_snapshot, log);
11121116

11131117
case search_strategy_t::COEFFICIENT_DIVING: {
1114-
logger_t log;
1115-
log.log = false;
11161118
return coefficient_diving<i_t, f_t>(this->worker.leaf_problem,
11171119
fractional,
11181120
x,
@@ -1121,7 +1123,7 @@ struct deterministic_diving_policy_t
11211123
log);
11221124
}
11231125

1124-
default: return this->worker.variable_selection_from_snapshot(fractional, x);
1126+
default: CUOPT_LOG_ERROR("Invalid diving method!"); return {-1, rounding_direction_t::NONE};
11251127
}
11261128
}
11271129

@@ -3366,11 +3368,12 @@ template <typename PoolT>
33663368
void branch_and_bound_t<i_t, f_t>::deterministic_broadcast_snapshots(
33673369
PoolT& pool, const std::vector<f_t>& incumbent_snapshot)
33683370
{
3369-
deterministic_snapshot_t<i_t, f_t> snap;
3370-
snap.upper_bound = upper_bound_.load();
3371-
snap.total_lp_iters = exploration_stats_.total_lp_iters.load();
3372-
snap.incumbent = incumbent_snapshot;
3373-
snap.pc_snapshot = pc_.create_snapshot();
3371+
deterministic_snapshot_t<i_t, f_t> snap{
3372+
.upper_bound = upper_bound_,
3373+
.pc_snapshot = pc_,
3374+
.incumbent = incumbent_snapshot,
3375+
.total_lp_iters = exploration_stats_.total_lp_iters,
3376+
};
33743377

33753378
for (auto& worker : pool) {
33763379
worker.set_snapshots(snap);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/* clang-format off */
2+
/*
3+
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
/* clang-format on */
7+
8+
#pragma once
9+
10+
namespace cuopt::linear_programming::dual_simplex {
11+
12+
constexpr int num_search_strategies = 5;
13+
14+
// Indicate the search and variable selection algorithms used by each thread
15+
// in B&B (See [1]).
16+
//
17+
// [1] T. Achterberg, “Constraint Integer Programming,” PhD, Technischen Universität Berlin,
18+
// Berlin, 2007. doi: 10.14279/depositonce-1634.
19+
enum search_strategy_t : int {
20+
BEST_FIRST = 0, // Best-First + Plunging.
21+
PSEUDOCOST_DIVING = 1, // Pseudocost diving (9.2.5)
22+
LINE_SEARCH_DIVING = 2, // Line search diving (9.2.4)
23+
GUIDED_DIVING = 3, // Guided diving (9.2.3).
24+
COEFFICIENT_DIVING = 4 // Coefficient diving (9.2.1)
25+
};
26+
27+
enum class rounding_direction_t { NONE = -1, DOWN = 0, UP = 1 };
28+
29+
enum class branch_and_bound_mode_t { REGULAR = 0, DETERMINISTIC = 1 };
30+
31+
} // namespace cuopt::linear_programming::dual_simplex

cpp/src/branch_and_bound/deterministic_workers.hpp

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ struct deterministic_snapshot_t {
5858
f_t upper_bound;
5959
pseudo_cost_snapshot_t<i_t, f_t> pc_snapshot;
6060
std::vector<f_t> incumbent;
61-
i_t total_lp_iters;
61+
int64_t total_lp_iters;
6262
};
6363

6464
template <typename i_t, typename f_t, typename Derived>
@@ -90,7 +90,7 @@ class deterministic_worker_base_t : public branch_and_bound_worker_t<i_t, f_t> {
9090
const std::vector<variable_type_t>& var_types,
9191
const simplex_solver_settings_t<i_t, f_t>& settings,
9292
const std::string& context_name)
93-
: base_t(id, original_lp, Arow, var_types, settings), work_context(context_name)
93+
: base_t(id, original_lp, Arow, var_types, settings), work_context(context_name), pc_snapshot(1)
9494
{
9595
work_context.deterministic = true;
9696
}
@@ -345,22 +345,6 @@ class deterministic_diving_worker_t
345345
{objective, solution, depth, this->worker_id, this->next_solution_seq++});
346346
++this->total_integer_solutions;
347347
}
348-
349-
branch_variable_t<i_t> variable_selection_from_snapshot(const std::vector<i_t>& fractional,
350-
const std::vector<f_t>& solution) const
351-
{
352-
assert(root_solution != nullptr);
353-
return this->pc_snapshot.pseudocost_diving(fractional, solution, *root_solution);
354-
}
355-
356-
branch_variable_t<i_t> guided_variable_selection(const std::vector<i_t>& fractional,
357-
const std::vector<f_t>& solution) const
358-
{
359-
if (this->incumbent_snapshot.empty()) {
360-
return variable_selection_from_snapshot(fractional, solution);
361-
}
362-
return this->pc_snapshot.guided_diving(fractional, solution, this->incumbent_snapshot);
363-
}
364348
};
365349

366350
template <typename i_t, typename f_t, typename WorkerT, typename Derived>

cpp/src/branch_and_bound/diving_heuristics.cpp

Lines changed: 113 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -65,38 +65,117 @@ branch_variable_t<i_t> line_search_diving(const std::vector<i_t>& fractional,
6565
return {branch_var, round_dir};
6666
}
6767

68-
template <typename i_t, typename f_t>
69-
branch_variable_t<i_t> pseudocost_diving(pseudo_costs_t<i_t, f_t>& pc,
68+
template <typename i_t, typename f_t, branch_and_bound_mode_t Mode>
69+
branch_variable_t<i_t> pseudocost_diving(pseudo_costs_t<i_t, f_t, Mode>& pc,
7070
const std::vector<i_t>& fractional,
7171
const std::vector<f_t>& solution,
7272
const std::vector<f_t>& root_solution,
7373
logger_t& log)
7474
{
75-
return pseudocost_diving_from_arrays(pc.pseudo_cost_sum_down.data(),
76-
pc.pseudo_cost_sum_up.data(),
77-
pc.pseudo_cost_num_down.data(),
78-
pc.pseudo_cost_num_up.data(),
79-
(i_t)pc.pseudo_cost_sum_down.size(),
80-
fractional,
81-
solution,
82-
root_solution);
75+
const i_t num_fractional = fractional.size();
76+
if (num_fractional == 0) return {-1, rounding_direction_t::NONE};
77+
78+
pseudo_cost_averages_t<i_t, f_t> avgs = pc.compute_averages();
79+
80+
i_t branch_var = fractional[0];
81+
f_t max_score = std::numeric_limits<f_t>::lowest();
82+
rounding_direction_t round_dir = rounding_direction_t::DOWN;
83+
constexpr f_t eps = f_t(1e-6);
84+
85+
for (i_t j : fractional) {
86+
f_t f_down = solution[j] - std::floor(solution[j]);
87+
f_t f_up = std::ceil(solution[j]) - solution[j];
88+
f_t pc_down = pc.pseudo_cost_num_down[j] != 0
89+
? pc.pseudo_cost_sum_down[j] / pc.pseudo_cost_num_down[j]
90+
: avgs.down_avg;
91+
f_t pc_up = pc.pseudo_cost_num_up[j] != 0 ? pc.pseudo_cost_sum_up[j] / pc.pseudo_cost_num_up[j]
92+
: avgs.up_avg;
93+
94+
f_t score_down = std::sqrt(f_up) * (1 + pc_up) / (1 + pc_down);
95+
f_t score_up = std::sqrt(f_down) * (1 + pc_down) / (1 + pc_up);
96+
97+
f_t score = 0;
98+
rounding_direction_t dir = rounding_direction_t::DOWN;
99+
100+
f_t root_val = (j < static_cast<i_t>(root_solution.size())) ? root_solution[j] : solution[j];
101+
102+
if (solution[j] < root_val - f_t(0.4)) {
103+
score = score_down;
104+
dir = rounding_direction_t::DOWN;
105+
} else if (solution[j] > root_val + f_t(0.4)) {
106+
score = score_up;
107+
dir = rounding_direction_t::UP;
108+
} else if (f_down < f_t(0.3)) {
109+
score = score_down;
110+
dir = rounding_direction_t::DOWN;
111+
} else if (f_down > f_t(0.7)) {
112+
score = score_up;
113+
dir = rounding_direction_t::UP;
114+
} else if (pc_down < pc_up + eps) {
115+
score = score_down;
116+
dir = rounding_direction_t::DOWN;
117+
} else {
118+
score = score_up;
119+
dir = rounding_direction_t::UP;
120+
}
121+
122+
if (score > max_score) {
123+
max_score = score;
124+
branch_var = j;
125+
round_dir = dir;
126+
}
127+
}
128+
129+
if (round_dir == rounding_direction_t::NONE) {
130+
branch_var = fractional[0];
131+
round_dir = rounding_direction_t::DOWN;
132+
}
133+
134+
return {branch_var, round_dir};
83135
}
84136

85-
template <typename i_t, typename f_t>
86-
branch_variable_t<i_t> guided_diving(pseudo_costs_t<i_t, f_t>& pc,
137+
template <typename i_t, typename f_t, branch_and_bound_mode_t Mode>
138+
branch_variable_t<i_t> guided_diving(pseudo_costs_t<i_t, f_t, Mode>& pc,
87139
const std::vector<i_t>& fractional,
88140
const std::vector<f_t>& solution,
89141
const std::vector<f_t>& incumbent,
90142
logger_t& log)
91143
{
92-
return guided_diving_from_arrays(pc.pseudo_cost_sum_down.data(),
93-
pc.pseudo_cost_sum_up.data(),
94-
pc.pseudo_cost_num_down.data(),
95-
pc.pseudo_cost_num_up.data(),
96-
(i_t)pc.pseudo_cost_sum_down.size(),
97-
fractional,
98-
solution,
99-
incumbent);
144+
const i_t num_fractional = fractional.size();
145+
if (num_fractional == 0) return {-1, rounding_direction_t::NONE};
146+
147+
pseudo_cost_averages_t<i_t, f_t> avgs = pc.compute_averages();
148+
149+
i_t branch_var = fractional[0];
150+
f_t max_score = std::numeric_limits<f_t>::lowest();
151+
rounding_direction_t round_dir = rounding_direction_t::DOWN;
152+
constexpr f_t eps = f_t(1e-6);
153+
154+
for (i_t j : fractional) {
155+
f_t f_down = solution[j] - std::floor(solution[j]);
156+
f_t f_up = std::ceil(solution[j]) - solution[j];
157+
f_t down_dist = std::abs(incumbent[j] - std::floor(solution[j]));
158+
f_t up_dist = std::abs(std::ceil(solution[j]) - incumbent[j]);
159+
rounding_direction_t dir =
160+
down_dist < up_dist + eps ? rounding_direction_t::DOWN : rounding_direction_t::UP;
161+
162+
f_t pc_down = pc.pseudo_cost_num_down[j] != 0
163+
? pc.pseudo_cost_sum_down[j] / pc.pseudo_cost_num_down[j]
164+
: avgs.down_avg;
165+
f_t pc_up = pc.pseudo_cost_num_up[j] != 0 ? pc.pseudo_cost_sum_up[j] / pc.pseudo_cost_num_up[j]
166+
: avgs.up_avg;
167+
f_t score1 = dir == rounding_direction_t::DOWN ? 5 * pc_down * f_down : 5 * pc_up * f_up;
168+
f_t score2 = dir == rounding_direction_t::DOWN ? pc_up * f_up : pc_down * f_down;
169+
f_t score = (score1 + score2) / 6;
170+
171+
if (score > max_score) {
172+
max_score = score;
173+
branch_var = j;
174+
round_dir = dir;
175+
}
176+
}
177+
178+
return {branch_var, round_dir};
100179
}
101180

102181
template <typename i_t, typename f_t>
@@ -187,12 +266,26 @@ template branch_variable_t<int> pseudocost_diving(pseudo_costs_t<int, double>& p
187266
const std::vector<double>& root_solution,
188267
logger_t& log);
189268

269+
template branch_variable_t<int> pseudocost_diving(
270+
pseudo_costs_t<int, double, branch_and_bound_mode_t::DETERMINISTIC>& pc,
271+
const std::vector<int>& fractional,
272+
const std::vector<double>& solution,
273+
const std::vector<double>& root_solution,
274+
logger_t& log);
275+
190276
template branch_variable_t<int> guided_diving(pseudo_costs_t<int, double>& pc,
191277
const std::vector<int>& fractional,
192278
const std::vector<double>& solution,
193279
const std::vector<double>& incumbent,
194280
logger_t& log);
195281

282+
template branch_variable_t<int> guided_diving(
283+
pseudo_costs_t<int, double, branch_and_bound_mode_t::DETERMINISTIC>& pc,
284+
const std::vector<int>& fractional,
285+
const std::vector<double>& solution,
286+
const std::vector<double>& incumbent,
287+
logger_t& log);
288+
196289
template void calculate_variable_locks(const lp_problem_t<int, double>& lp_problem,
197290
std::vector<int>& up_locks,
198291
std::vector<int>& down_locks);

cpp/src/branch_and_bound/diving_heuristics.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ branch_variable_t<i_t> line_search_diving(const std::vector<i_t>& fractional,
2222
const std::vector<f_t>& root_solution,
2323
logger_t& log);
2424

25-
template <typename i_t, typename f_t>
26-
branch_variable_t<i_t> pseudocost_diving(pseudo_costs_t<i_t, f_t>& pc,
25+
template <typename i_t, typename f_t, branch_and_bound_mode_t Mode>
26+
branch_variable_t<i_t> pseudocost_diving(pseudo_costs_t<i_t, f_t, Mode>& pc,
2727
const std::vector<i_t>& fractional,
2828
const std::vector<f_t>& solution,
2929
const std::vector<f_t>& root_solution,
3030
logger_t& log);
3131

32-
template <typename i_t, typename f_t>
33-
branch_variable_t<i_t> guided_diving(pseudo_costs_t<i_t, f_t>& pc,
32+
template <typename i_t, typename f_t, branch_and_bound_mode_t Mode>
33+
branch_variable_t<i_t> guided_diving(pseudo_costs_t<i_t, f_t, Mode>& pc,
3434
const std::vector<i_t>& fractional,
3535
const std::vector<f_t>& solution,
3636
const std::vector<f_t>& incumbent,

cpp/src/branch_and_bound/mip_node.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#pragma once
99

10+
#include <branch_and_bound/constants.hpp>
11+
1012
#include <dual_simplex/initial_basis.hpp>
1113
#include <dual_simplex/types.hpp>
1214

@@ -29,8 +31,6 @@ enum class node_status_t : int {
2931
NUMERICAL = 5 // Encountered numerical issue when solving the LP relaxation
3032
};
3133

32-
enum class rounding_direction_t : int8_t { NONE = -1, DOWN = 0, UP = 1 };
33-
3434
inline bool inactive_status(node_status_t status)
3535
{
3636
return (status == node_status_t::FATHOMED || status == node_status_t::INTEGER_FEASIBLE ||

0 commit comments

Comments
 (0)