5#ifndef IDOL_OPTIMIZERS_BRANCHANDBOUND_H
6#define IDOL_OPTIMIZERS_BRANCHANDBOUND_H
8#include "idol/mixed-integer/optimizers/branch-and-bound/nodes/NodeSet.h"
9#include "idol/general/optimizers/Algorithm.h"
10#include "idol/mixed-integer/optimizers/branch-and-bound/branching-rules/factories/BranchingRuleFactory.h"
11#include "idol/mixed-integer/optimizers/branch-and-bound/node-selection-rules/factories/NodeSelectionRuleFactory.h"
12#include "idol/mixed-integer/optimizers/branch-and-bound/branching-rules/impls/BranchingRule.h"
13#include "idol/mixed-integer/optimizers/branch-and-bound/node-selection-rules/impls/NodeSelectionRule.h"
14#include "idol/mixed-integer/optimizers/branch-and-bound/nodes/Node.h"
15#include "idol/mixed-integer/optimizers/branch-and-bound/nodes/NodeUpdator.h"
16#include "idol/mixed-integer/optimizers/branch-and-bound/callbacks/AbstractBranchAndBoundCallbackI.h"
17#include "idol/general/optimizers/OptimizerFactory.h"
18#include "idol/mixed-integer/modeling/models/Model.h"
19#include "idol/mixed-integer/optimizers/branch-and-bound/logs/Factory.h"
20#include "idol/mixed-integer/optimizers/branch-and-bound/CutPool.h"
26#include "idol/general/utils/Queue.h"
27#include "idol/mixed-integer/optimizers/presolve/Presolve.h"
29namespace idol::Optimizers {
33template<
class NodeInfoT>
38 std::unique_ptr<OptimizerFactory> m_relaxation_optimizer_factory;
40 unsigned int m_n_threads = 1;
41 unsigned int m_solution_pool_size = 10;
42 unsigned int m_current_solution_index = 0;
43 unsigned int m_n_best_incumbent_index = -1;
44 std::unique_ptr<Model> m_presolved_model;
45 std::vector<std::unique_ptr<Model>> m_relaxations;
46 std::vector<std::unique_ptr<NodeUpdator<NodeInfoT>>> m_node_updators;
48 std::unique_ptr<BranchingRule<NodeInfoT>> m_branching_rule;
49 std::unique_ptr<NodeSelectionRule<NodeInfoT>> m_node_selection_rule;
50 std::unique_ptr<typename Logs::BranchAndBound::Factory<NodeInfoT>::Strategy> m_logger;
51 std::unique_ptr<NodeInfoT> m_root_node_info;
53 std::unique_ptr<AbstractBranchAndBoundCallbackI<NodeInfoT>> m_callback;
61 bool m_has_integer_objective =
false;
62 std::vector<unsigned int> m_steps = { std::numeric_limits<unsigned int>::max(), 0, 0 };
63 unsigned int m_n_created_nodes = 0;
64 unsigned int m_n_solved_nodes = 0;
65 unsigned int m_n_active_nodes = 0;
66 double m_root_node_best_bound = -Inf;
67 double m_root_node_best_obj = +Inf;
71 [[nodiscard]]
const Model& working_model()
const {
return m_presolved_model ? *m_presolved_model : parent(); }
73 void build()
override;
74 void hook_before_optimize()
override;
75 void hook_optimize()
override;
76 void hook_after_optimize()
override;
77 void add(
const Var &t_var)
override;
78 void add(
const Ctr &t_ctr)
override;
79 void add(
const QCtr &t_ctr)
override;
80 void remove(
const Var &t_var)
override;
81 void remove(
const Ctr &t_ctr)
override;
82 void remove(
const QCtr &t_ctr)
override;
83 void update()
override;
84 void write(
const std::string &t_name)
override;
86 void detect_integer_objective();
87 void create_relaxations();
89 void explore(TreeNode& t_node,
unsigned int t_relaxation_id, SetOfActiveNodes& t_active_nodes,
unsigned int t_step);
90 void analyze(
const TreeNode& t_node,
unsigned int t_relaxation_id,
bool* t_explore_children_flag,
bool* t_reoptimize_flag);
91 Node<NodeInfoT> select_node_for_branching(SetOfActiveNodes& t_active_nodes);
92 std::vector<TreeNode> create_child_nodes(
const TreeNode& t_node);
93 void backtrack(SetOfActiveNodes& t_actives_nodes, SetOfActiveNodes& t_sub_active_nodes);
94 void set_as_incumbent(
const TreeNode& t_node);
95 [[nodiscard]]
bool gap_is_closed()
const;
96 void prune_nodes_by_bound(SetOfActiveNodes& t_active_nodes);
97 void update_lower_bound(
const SetOfActiveNodes& t_active_nodes);
98 bool is_valid(
const TreeNode& t_node)
const;
101 void log_after_termination();
103 SideEffectRegistry call_callbacks(CallbackEvent t_event,
const TreeNode& t_node,
unsigned int t_relaxation_id);
104 void update_obj_sense()
override;
105 void update_obj()
override;
106 void update_rhs()
override;
107 void update_obj_constant()
override;
108 void update_mat_coeff(
const Ctr &t_ctr,
const Var &t_var)
override;
109 void update_ctr_type(
const Ctr &t_ctr)
override;
110 void update_ctr_rhs(
const Ctr &t_ctr)
override;
111 void update_var_type(
const Var &t_var)
override;
112 void update_var_lb(
const Var &t_var)
override;
113 void update_var_ub(
const Var &t_var)
override;
115 void update_var_obj(
const Var &t_var)
override;
116 void set_status(SolutionStatus t_status)
override;
117 void set_reason(SolutionReason t_reason)
override;
118 void set_best_bound(
double t_value)
override;
119 void set_best_obj(
double t_value)
override;
121 explicit BranchAndBound(
const Model& t_model,
128 [[nodiscard]] std::string name()
const override {
return "Branch-and-Bound"; }
130 void solve(TreeNode& t_node,
unsigned int t_relaxation_id);
132 virtual void set_subtree_depth(
unsigned int t_depth) { m_steps.at(1) = t_depth; }
134 [[nodiscard]]
unsigned int subtree_depth()
const {
return m_steps.at(1); }
140 void submit_heuristic_solution(NodeInfoT* t_info);
142 void submit_lower_bound(
double t_lower_bound);
145 bool add_lazy_cut(
const TempCtr &t_cut);
148 bool add_user_cut(
const TempCtr &t_cut);
150 [[nodiscard]]
unsigned int n_created_nodes()
const {
return m_n_created_nodes; }
152 [[nodiscard]]
unsigned int n_solved_nodes()
const {
return m_n_solved_nodes; }
154 [[nodiscard]]
unsigned int n_active_nodes()
const {
return m_n_active_nodes; }
156 [[nodiscard]]
const Model& relaxation()
const {
return *m_relaxations.front(); }
158 [[nodiscard]] Model& relaxation() {
return *m_relaxations.front(); }
160 [[nodiscard]]
double root_node_best_bound()
const {
return m_root_node_best_bound; }
162 [[nodiscard]]
double root_node_best_obj()
const {
return m_root_node_best_obj; }
168 [[nodiscard]]
bool has_incumbent()
const {
return !m_incumbents.empty(); }
170 const TreeNode& incumbent()
const {
return m_incumbents.at(m_current_solution_index); }
172 [[nodiscard]]
double get_var_primal(
const Var &t_var)
const override;
174 [[nodiscard]]
double get_var_reduced_cost(
const Var &t_var)
const override;
176 [[nodiscard]]
double get_var_ray(
const Var &t_var)
const override;
178 [[nodiscard]]
double get_ctr_dual(
const Ctr &t_ctr)
const override;
180 [[nodiscard]]
double get_ctr_farkas(
const Ctr &t_ctr)
const override;
182 [[nodiscard]]
unsigned int get_n_solutions()
const override;
184 [[nodiscard]]
unsigned int get_solution_index()
const override;
186 void set_solution_index(
unsigned int t_index)
override;
188 void terminate()
override;
192 void set_root_node_info(
const NodeInfoT& t_node_info);
194 [[nodiscard]]
const OptimizerFactory& get_relaxation_optimizer_factory()
const;
196 [[nodiscard]]
double get_best_obj()
const override;
199template<
class NodeInfoT>
200void idol::Optimizers::BranchAndBound<NodeInfoT>::remove(
const idol::QCtr &t_ctr) {
201 for (
auto& relaxation : m_relaxations) {
202 relaxation->remove(t_ctr);
206template<
class NodeInfoT>
207void idol::Optimizers::BranchAndBound<NodeInfoT>::add(
const idol::QCtr &t_ctr) {
208 for (
auto& relaxation : m_relaxations) {
209 relaxation->add(t_ctr);
213template<
class NodeInfoT>
214bool idol::Optimizers::BranchAndBound<NodeInfoT>::is_valid(
const TreeNode &t_node)
const {
217 result = m_branching_rule->is_valid(t_node);
221template<
class NodeInfoT>
222void idol::Optimizers::BranchAndBound<NodeInfoT>::terminate() {
224 Optimizer::terminate();
227template <
class NodeInfoT>
void idol::Optimizers::BranchAndBound<NodeInfoT>::set_relaxation_optimizer_factory(
228 const OptimizerFactory& t_factory) {
230 m_relaxation_optimizer_factory.reset(t_factory.clone());
234template <
class NodeInfoT>
235void idol::Optimizers::BranchAndBound<NodeInfoT>::set_root_node_info(
const NodeInfoT& t_node_info) {
236 m_root_node_info.reset(t_node_info.clone());
241 return *m_relaxation_optimizer_factory;
244template <
class NodeInfoT>
245double idol::Optimizers::BranchAndBound<NodeInfoT>::get_best_obj()
const {
246 if (m_current_solution_index == 0) {
247 return Algorithm::get_best_obj();
249 return m_incumbents.at(m_current_solution_index).info().objective_value();
252template<
class NodeInfoT>
253void idol::Optimizers::BranchAndBound<NodeInfoT>::detect_integer_objective() {
254 const auto& src_model = working_model();
255 const auto& objective = src_model.get_obj_expr();
257 if (!is_integer(objective.affine().constant(), get_tol_integer())) {
258 m_has_integer_objective =
false;
262 for (
const auto& [var, val] : objective.affine().linear()) {
263 if (src_model.get_var_type(var) == Continuous || !is_integer(val, get_tol_integer())) {
264 m_has_integer_objective =
false;
269 m_has_integer_objective =
true;
272template<
class NodeInfoT>
273double idol::Optimizers::BranchAndBound<NodeInfoT>::get_var_reduced_cost(
const idol::Var &t_var)
const {
274 if (m_n_solved_nodes > 1) {
275 throw Exception(
"Reduced cost not available.");
277 return m_relaxations.front()->get_var_reduced_cost(t_var);
280template<
class NodeInfoT>
281void idol::Optimizers::BranchAndBound<NodeInfoT>::log_after_termination() {
283 if (!get_param_logs()) {
288 m_callback->log_after_termination();
291 m_logger->log_after_termination();
295template<
class NodeInfoT>
296void idol::Optimizers::BranchAndBound<NodeInfoT>::log_node_after_solve(
const idol::Node<NodeInfoT> &t_node) {
297 m_logger->log_node_after_solve(t_node);
300template<
class NodeInfoT>
301void idol::Optimizers::BranchAndBound<NodeInfoT>::set_solution_index(
unsigned int t_index) {
302 if (t_index > m_incumbents.size()) {
303 throw Exception(
"Solution index out of bounds.");
305 m_current_solution_index = t_index;
308template<
class NodeInfoT>
309unsigned int idol::Optimizers::BranchAndBound<NodeInfoT>::get_solution_index()
const {
313template<
class NodeInfoT>
314unsigned int idol::Optimizers::BranchAndBound<NodeInfoT>::get_n_solutions()
const {
315 return m_incumbents.size();
318template<
class NodeInfoT>
319void idol::Optimizers::BranchAndBound<NodeInfoT>::update_obj() {
320 for (
auto& relaxation : m_relaxations) {
321 relaxation->set_obj_expr(working_model().get_obj_expr());
325template<
class NodeInfoT>
326void idol::Optimizers::BranchAndBound<NodeInfoT>::update_rhs() {
327 for (
auto& relaxation : m_relaxations) {
328 relaxation->set_rhs_expr(working_model().get_rhs_expr());
332template<
class NodeInfoT>
333void idol::Optimizers::BranchAndBound<NodeInfoT>::update_obj_constant() {
334 for (
auto& relaxation : m_relaxations) {
335 relaxation->set_obj_const(working_model().get_obj_expr().affine().constant());
339template<
class NodeInfoT>
340void idol::Optimizers::BranchAndBound<NodeInfoT>::update_mat_coeff(
const Ctr &t_ctr,
const Var &t_var) {
341 for (
auto& relaxation : m_relaxations) {
342 relaxation->set_mat_coeff(t_ctr, t_var, working_model().get_mat_coeff(t_ctr, t_var));
346template<
class NodeInfoT>
347void idol::Optimizers::BranchAndBound<NodeInfoT>::update_ctr_type(
const Ctr &t_ctr) {
348 for (
auto& relaxation : m_relaxations) {
349 relaxation->set_ctr_type(t_ctr, working_model().get_ctr_type(t_ctr));
353template<
class NodeInfoT>
354void idol::Optimizers::BranchAndBound<NodeInfoT>::update_ctr_rhs(
const Ctr &t_ctr) {
355 for (
auto& relaxation : m_relaxations) {
356 relaxation->set_ctr_rhs(t_ctr, working_model().get_ctr_rhs(t_ctr));
360template<
class NodeInfoT>
361void idol::Optimizers::BranchAndBound<NodeInfoT>::update_var_type(
const Var &t_var) {
362 for (
auto& relaxation : m_relaxations) {
363 relaxation->set_var_type(t_var, working_model().get_var_type(t_var));
367template<
class NodeInfoT>
368void idol::Optimizers::BranchAndBound<NodeInfoT>::update_var_lb(
const Var &t_var) {
369 for (
auto& relaxation : m_relaxations) {
370 relaxation->set_var_lb(t_var, working_model().get_var_lb(t_var));
374template<
class NodeInfoT>
375void idol::Optimizers::BranchAndBound<NodeInfoT>::update_var_ub(
const Var &t_var) {
376 for (
auto& relaxation : m_relaxations) {
377 relaxation->set_var_ub(t_var, working_model().get_var_ub(t_var));
381template<
class NodeInfoT>
382void idol::Optimizers::BranchAndBound<NodeInfoT>::update_var_obj(
const Var &t_var) {
383 for (
auto& relaxation : m_relaxations) {
384 relaxation->set_var_obj(t_var, working_model().get_var_obj(t_var));
388template<
class NodeInfoT>
389void idol::Optimizers::BranchAndBound<NodeInfoT>::update_obj_sense() {
390 throw Exception(
"Not implemented");
393template<
class NodeInfoT>
394double idol::Optimizers::BranchAndBound<NodeInfoT>::get_ctr_farkas(
const Ctr &t_ctr)
const {
395 if (m_n_solved_nodes > 1) {
396 throw Exception(
"Accessing Farkas certificate for MIP is not available after the root node.");
398 return m_relaxations.front()->get_ctr_farkas(t_ctr);
401template<
class NodeInfoT>
402double idol::Optimizers::BranchAndBound<NodeInfoT>::get_ctr_dual(
const Ctr &t_ctr)
const {
403 if (m_n_solved_nodes > 1) {
404 throw Exception(
"Accessing dual values for MIP is not available after the root node.");
406 return m_relaxations.front()->get_ctr_dual(t_ctr);
409template<
class NodeInfoT>
410double idol::Optimizers::BranchAndBound<NodeInfoT>::get_var_ray(
const Var &t_var)
const {
411 if (m_n_solved_nodes > 1) {
412 throw Exception(
"Ray not available.");
414 return m_relaxations.front()->get_var_ray(t_var);
417template<
class NodeInfoT>
418double idol::Optimizers::BranchAndBound<NodeInfoT>::get_var_primal(
const Var &t_var)
const {
420 if (!has_incumbent()){
421 throw Exception(
"Trying to access primal values while no incumbent was found.");
424 return incumbent().info().primal_solution().get(t_var);
427template<
class NodeInfoT>
428void idol::Optimizers::BranchAndBound<NodeInfoT>::submit_lower_bound(
double t_lower_bound) {
429 if (t_lower_bound > get_best_obj()) {
431 set_reason(NotSpecified);
435 if (t_lower_bound > get_best_bound()) {
436 set_best_bound(t_lower_bound);
441template <
class NodeInfoT>
442bool idol::Optimizers::BranchAndBound<NodeInfoT>::add_lazy_cut(
const TempCtr& t_cut) {
444 m_relaxations.front()->add_ctr(t_cut);
448template <
class NodeInfoT>
449bool idol::Optimizers::BranchAndBound<NodeInfoT>::add_user_cut(
const TempCtr& t_cut) {
450 return m_user_cut_pool.add_cut(t_cut, *m_relaxations.front());
453template<
class NodeInfoT>
454void idol::Optimizers::BranchAndBound<NodeInfoT>::submit_heuristic_solution(NodeInfoT* t_info) {
456 auto t_node = Node<NodeInfoT>::create_detached_node(t_info);
458 if (t_node.info().objective_value() > get_best_obj()) {
462 const auto registry = call_callbacks(IncumbentSolution, t_node, -1);
464 if (registry.n_added_lazy_cuts > 0) {
468 set_as_incumbent(t_node);
469 log_node_after_solve(t_node);
477template<
class NodeInfoT>
479idol::Optimizers::BranchAndBound<NodeInfoT>::call_callbacks(CallbackEvent t_event,
const Optimizers::BranchAndBound<NodeInfoT>::TreeNode &t_node,
unsigned int t_relaxation_id) {
485 Model* relaxation =
nullptr;
486 if (t_relaxation_id != -1) {
487 relaxation = m_relaxations[t_relaxation_id].get();
490 return m_callback->operator()(
this, t_event, t_node, relaxation);
493template<
class NodeInfoT>
494void idol::Optimizers::BranchAndBound<NodeInfoT>::add_callback(BranchAndBoundCallback<NodeInfoT> *t_cb) {
495 m_callback->add_callback(t_cb);
498template <
class NodeInfoT>
499void idol::Optimizers::BranchAndBound<NodeInfoT>::add_presolver(
const Presolvers::AbstractPresolver& t_presolver) {
500 m_presolve.add(t_presolver);
503template<
class NodeInfoT>
504void idol::Optimizers::BranchAndBound<NodeInfoT>::build() {
508template<
class NodeInfoT>
509idol::Optimizers::BranchAndBound<NodeInfoT>::BranchAndBound(
const Model &t_model,
510 const OptimizerFactory& t_node_optimizer,
511 const BranchingRuleFactory<NodeInfoT>& t_branching_rule_factory,
512 const NodeSelectionRuleFactory<NodeInfoT>& t_node_selection_rule_factory,
513 AbstractBranchAndBoundCallbackI<NodeInfoT>* t_callback,
514 const Logs::BranchAndBound::Factory<NodeInfoT>& t_logger_factory)
515 : Algorithm(t_model),
516 m_relaxation_optimizer_factory(t_node_optimizer.clone()),
517 m_branching_rule(t_branching_rule_factory(*this)),
518 m_node_selection_rule(t_node_selection_rule_factory(*this)),
519 m_callback(t_callback),
520 m_logger(t_logger_factory.operator()(*this)),
521 m_incumbents(m_solution_pool_size) {
525template<
class NodeInfoT>
526void idol::Optimizers::BranchAndBound<NodeInfoT>::create_relaxations() {
528 const auto &original_model = working_model();
530 m_relaxations.clear();
531 m_relaxations.reserve(m_n_threads);
533 m_node_updators.clear();
534 m_node_updators.reserve(m_n_threads);
536 for (
unsigned int i = 0 ; i < m_n_threads ; ++i) {
537 auto* relaxation = original_model.clone();
538 relaxation->use(*m_relaxation_optimizer_factory);
539 m_relaxations.emplace_back(relaxation);
540 m_node_updators.emplace_back(
dynamic_cast<NodeUpdator<NodeInfoT>*
>(NodeInfoT::create_updator(working_model(), *relaxation)));
545template<
class NodeInfoT>
546idol::Node<NodeInfoT> idol::Optimizers::BranchAndBound<NodeInfoT>::create_root_node() {
548 NodeInfoT* node_info =
nullptr;
549 if (m_root_node_info) {
550 node_info = m_root_node_info->clone();
551 }
else if constexpr (std::is_default_constructible_v<NodeInfoT>) {
552 node_info =
new NodeInfoT();
554 throw Exception(
"The given node info class has no default constructor and no root node info has been given.");
556 auto root_node = Node<NodeInfoT>::create_root_node(node_info);
557 assert(root_node.id() == 0);
564template<
class NodeInfoT>
565void idol::Optimizers::BranchAndBound<NodeInfoT>::hook_before_optimize() {
566 Algorithm::hook_before_optimize();
569 set_best_bound(std::max(-Inf, get_param_best_obj_stop()));
570 set_best_obj(std::min(+Inf, get_param_best_bound_stop()));
571 m_incumbents.clear();
573 detect_integer_objective();
575 m_n_created_nodes = 0;
576 m_n_solved_nodes = 0;
577 m_n_active_nodes = 0;
578 m_current_solution_index = 0;
580 m_root_node_best_bound = -Inf;
581 m_root_node_best_obj = Inf;
585template<
class NodeInfoT>
586void idol::Optimizers::BranchAndBound<NodeInfoT>::hook_optimize() {
588 if (!m_presolve.empty()) {
589 m_presolved_model.reset(working_model().clone());
590 m_presolve.execute(*m_presolved_model);
593 create_relaxations();
595 m_branching_rule->initialize();
596 for (
auto& node_updator : m_node_updators) {
597 node_updator->initialize();
599 m_callback->initialize(
this);
601 m_logger->initialize();
603 auto root_node = create_root_node();
605 SetOfActiveNodes active_nodes;
607 explore(root_node, 0, active_nodes, 0);
609 if (active_nodes.empty()) {
610 set_best_bound(get_best_obj());
613 m_node_updators[0]->clear();
615 if (get_status() == Fail) {
619 if (!has_incumbent()) {
621 if (is_pos_inf(get_best_obj())) {
622 set_status(Infeasible);
626 if (is_neg_inf(get_best_obj())) {
627 set_status(Unbounded);
633 set_status(Feasible);
635 if (gap_is_closed()) {
643template<
class NodeInfoT>
644void idol::Optimizers::BranchAndBound<NodeInfoT>::hook_after_optimize() {
646 Optimizer::hook_after_optimize();
648 log_after_termination();
652template<
class NodeInfoT>
653void idol::Optimizers::BranchAndBound<NodeInfoT>::explore(
655 unsigned int t_relaxation_id,
656 SetOfActiveNodes & t_active_nodes,
657 unsigned int t_step) {
659 if (is_terminated() || gap_is_closed()) {
return; }
661 bool reoptimize_flag;
662 bool explore_children_flag;
666 solve(t_node, t_relaxation_id);
668 analyze(t_node, t_relaxation_id, &explore_children_flag, &reoptimize_flag);
670 log_node_after_solve(t_node);
672 }
while (reoptimize_flag);
677 if (is_terminated() || gap_is_closed()) {
return; }
679 if (!explore_children_flag) {
return; }
681 t_active_nodes.emplace(t_node);
683 const unsigned int max_levels = m_steps.at(t_step);
685 for (
unsigned int level = 0 ; level < max_levels ; ++level) {
687 prune_nodes_by_bound(t_active_nodes);
690 update_lower_bound(t_active_nodes);
691 m_n_active_nodes = t_active_nodes.size();
694 if (t_active_nodes.empty()) {
break; }
696 if (is_terminated() || gap_is_closed()) {
break; }
698 auto selected_node = select_node_for_branching(t_active_nodes);
700 auto children = create_child_nodes(selected_node);
702 const unsigned int n_children = children.size();
704 std::vector<SetOfActiveNodes> active_nodes(n_children);
706 if (m_n_threads > 1 && t_step == 0) {
708 std::vector<std::vector<unsigned int>> to_explore(m_n_threads);
710 for (
unsigned int q = 0 ; q < n_children ; ++q) {
711 const unsigned int relaxation_id = q % m_n_threads;
712 to_explore[relaxation_id].emplace_back(q);
715#pragma omp parallel for num_threads(m_n_threads)
716 for (
unsigned int k = 0 ; k < m_n_threads ; ++k) {
717 for (
unsigned int j = 0, n_tasks = to_explore[k].size() ; j < n_tasks ; ++j) {
718 const unsigned int q = to_explore[k][j];
719 explore(children[q], k, active_nodes[q], t_step + 1);
725 for (
unsigned int q = 0 ; q < n_children ; ++q) {
726 explore(children[q], t_relaxation_id, active_nodes[q], t_step + 1);
731 for (
unsigned int q = 0 ; q < n_children ; ++q) {
732 backtrack(t_active_nodes, active_nodes[q]);
739template<
class NodeInfoT>
740void idol::Optimizers::BranchAndBound<NodeInfoT>::solve(TreeNode& t_node,
741 unsigned int t_relaxation_id) {
743 auto& node_updator = *m_node_updators[t_relaxation_id];
744 auto& relaxation = *m_relaxations[t_relaxation_id];
746 relaxation.optimizer().set_param_best_bound_stop(std::min(get_best_obj(), get_param_best_bound_stop()));
747 relaxation.optimizer().set_param_time_limit(get_remaining_time());
749 node_updator.prepare(t_node);
763 m_user_cut_pool.clean_up(relaxation);
765 relaxation.optimize();
767 t_node.info().save(working_model(), relaxation);
769 m_branching_rule->on_node_solved(t_node);
773template<
class NodeInfoT>
774void idol::Optimizers::BranchAndBound<NodeInfoT>::analyze(
const BranchAndBound::TreeNode &t_node,
unsigned int t_relaxation_id,
bool* t_explore_children_flag,
bool* t_reoptimize_flag) {
776 *t_explore_children_flag =
false;
777 *t_reoptimize_flag =
false;
779 if (get_best_bound() > get_best_obj()) {
781 set_reason(NotSpecified);
786 const auto& status = t_node.info().status();
787 const auto& reason = t_node.info().reason();
789 if (get_remaining_time() == 0 || reason == TimeLimit) {
790 set_reason(TimeLimit);
795 if (status == Unbounded) {
802 if (status == Infeasible || status == InfOrUnbnd) {
804 if (t_node.level() == 0) {
808 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
812 if (status == Feasible && reason == ObjLimit) {
813 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
817 if (status == Fail || status == Loaded) {
820 set_reason(NotSpecified);
822 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
826 if (t_node.level() == 0) {
827 m_root_node_best_bound = t_node.info().objective_value();
828 set_best_bound(std::max(m_root_node_best_bound, get_best_bound()));
831 if (t_node.info().objective_value() < get_best_obj()) {
833 if (is_valid(t_node)) {
835 auto side_effects = call_callbacks(IncumbentSolution, t_node, t_relaxation_id);
837 if (side_effects.n_added_user_cuts > 0 || side_effects.n_added_lazy_cuts > 0) {
838 *t_reoptimize_flag =
true;
842 set_as_incumbent(t_node);
849 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
854 const unsigned int recycled_user_cuts = m_user_cut_pool.recycle(t_node.info().primal_solution(), *m_relaxations[t_relaxation_id], get_tol_feasibility());
855 if (recycled_user_cuts > 0) {
856 *t_reoptimize_flag =
true;
860 auto side_effects = call_callbacks(InvalidSolution, t_node, t_relaxation_id);
862 if (t_node.level() == 0) {
863 m_root_node_best_obj = get_best_obj();
866 if (side_effects.n_added_lazy_cuts > 0 || side_effects.n_added_user_cuts > 0 || side_effects.n_added_local_variable_branching > 0) {
867 *t_reoptimize_flag =
true;
871 *t_explore_children_flag =
true;
875template<
class NodeInfoT>
876void idol::Optimizers::BranchAndBound<NodeInfoT>::set_as_incumbent(
const BranchAndBound::TreeNode &t_node) {
878 m_incumbents.push(t_node);
879 set_best_obj(t_node.info().objective_value());
880 set_status(Feasible);
883template<
class NodeInfoT>
884void idol::Optimizers::BranchAndBound<NodeInfoT>::update_lower_bound(
const BranchAndBound::SetOfActiveNodes &t_active_nodes) {
886 if (t_active_nodes.empty()) {
return; }
888 auto& lowest_node = *t_active_nodes.by_objective_value().begin();
889 const double raw_lower_bound = lowest_node.info().objective_value();
890 const double lower_bound = m_has_integer_objective ? std::ceil(raw_lower_bound - get_tol_integer()) : raw_lower_bound;
891 if (lower_bound > get_best_bound()) {
892 set_best_bound(lower_bound);
897template<
class NodeInfoT>
898void idol::Optimizers::BranchAndBound<NodeInfoT>::prune_nodes_by_bound(BranchAndBound::SetOfActiveNodes& t_active_nodes) {
900 const double upper_bound = get_best_obj();
902 auto it = t_active_nodes.by_objective_value().begin();
903 auto end = t_active_nodes.by_objective_value().end();
907 const auto& node = *it;
908 const double raw_lower_bound = node.info().objective_value();
909 const double lower_bound = m_has_integer_objective && !is_integer(raw_lower_bound, get_tol_integer()) ? std::ceil(raw_lower_bound) : raw_lower_bound;
911 if (lower_bound >= upper_bound - get_tol_mip_absolute_gap()) {
912 it = t_active_nodes.erase(it);
913 end = t_active_nodes.by_objective_value().end();
922template<
class NodeInfoT>
924idol::Optimizers::BranchAndBound<NodeInfoT>::select_node_for_branching(BranchAndBound::SetOfActiveNodes &t_active_nodes) {
925 auto iterator = m_node_selection_rule->operator()(t_active_nodes);
926 auto result = *iterator;
927 t_active_nodes.erase(iterator);
931template<
class NodeInfoT>
932void idol::Optimizers::BranchAndBound<NodeInfoT>::write(
const std::string &t_name) {
933 m_relaxations.front()->write(t_name);
936template<
class NodeInfoT>
937std::vector<idol::Node<NodeInfoT>> idol::Optimizers::BranchAndBound<NodeInfoT>::create_child_nodes(
const BranchAndBound::TreeNode &t_node) {
939 auto children_info = m_branching_rule->create_child_nodes(t_node);
941 std::vector<Node<NodeInfoT>> result;
942 result.reserve(children_info.size());
944 for (
auto* ptr_to_info : children_info) {
945 result.emplace_back(ptr_to_info, m_n_created_nodes++, t_node);
948 m_branching_rule->on_nodes_have_been_created();
953template<
class NodeInfoT>
954bool idol::Optimizers::BranchAndBound<NodeInfoT>::gap_is_closed()
const {
955 return get_relative_gap() <= get_tol_mip_relative_gap()
956 || get_absolute_gap() <= get_tol_mip_absolute_gap();
959template<
class NodeInfoT>
960void idol::Optimizers::BranchAndBound<NodeInfoT>::backtrack(BranchAndBound::SetOfActiveNodes &t_actives_nodes,
961 SetOfActiveNodes &t_sub_active_nodes) {
963 t_actives_nodes.merge(std::move(t_sub_active_nodes));
967template<
class NodeInfoT>
968void idol::Optimizers::BranchAndBound<NodeInfoT>::update() {
969 for (
auto& relaxation : m_relaxations) {
970 relaxation->update();
974template<
class NodeInfoT>
975void idol::Optimizers::BranchAndBound<NodeInfoT>::remove(
const Ctr &t_ctr) {
976 for (
auto& relaxation : m_relaxations) {
977 relaxation->remove(t_ctr);
981template<
class NodeInfoT>
982void idol::Optimizers::BranchAndBound<NodeInfoT>::remove(
const Var &t_var) {
983 for (
auto& relaxation : m_relaxations) {
984 relaxation->remove(t_var);
988template<
class NodeInfoT>
989void idol::Optimizers::BranchAndBound<NodeInfoT>::add(
const Ctr &t_ctr) {
990 for (
auto& relaxation : m_relaxations) {
991 relaxation->add(t_ctr);
995template<
class NodeInfoT>
996void idol::Optimizers::BranchAndBound<NodeInfoT>::add(
const Var &t_var) {
997 for (
auto& relaxation : m_relaxations) {
998 relaxation->add(t_var);
1002template<
class NodeInfoT>
1003void idol::Optimizers::BranchAndBound<NodeInfoT>::set_best_obj(
double t_value) {
1005 Algorithm::set_best_obj(t_value);
1006 assert(get_best_bound() <= get_best_obj() + get_tol_mip_absolute_gap());
1009template<
class NodeInfoT>
1010void idol::Optimizers::BranchAndBound<NodeInfoT>::set_best_bound(
double t_value) {
1012 Algorithm::set_best_bound(t_value);
1013 assert(get_best_bound() <= get_best_obj() + get_tol_mip_absolute_gap());
1016template<
class NodeInfoT>
1017void idol::Optimizers::BranchAndBound<NodeInfoT>::set_reason(idol::SolutionReason t_reason) {
1019 Algorithm::set_reason(t_reason);
1022template<
class NodeInfoT>
1023void idol::Optimizers::BranchAndBound<NodeInfoT>::set_status(idol::SolutionStatus t_status) {
1025 Algorithm::set_status(t_status);