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"
25namespace idol::Optimizers {
26 template<
class NodeInfoT>
class BranchAndBound;
29template<
class NodeInfoT>
34 std::unique_ptr<OptimizerFactory> m_relaxation_optimizer_factory;
36 unsigned int m_n_threads = 1;
37 std::vector<std::unique_ptr<Model>> m_relaxations;
38 std::vector<std::unique_ptr<NodeUpdator<NodeInfoT>>> m_node_updators;
40 std::unique_ptr<BranchingRule<NodeInfoT>> m_branching_rule;
41 std::unique_ptr<NodeSelectionRule<NodeInfoT>> m_node_selection_rule;
42 std::unique_ptr<typename Logs::BranchAndBound::Factory<NodeInfoT>::Strategy> m_logger;
44 std::unique_ptr<AbstractBranchAndBoundCallbackI<NodeInfoT>> m_callback;
46 bool m_has_integer_objective =
false;
47 std::vector<unsigned int> m_steps = { std::numeric_limits<unsigned int>::max(), 0, 0 };
48 unsigned int m_n_created_nodes = 0;
49 unsigned int m_n_solved_nodes = 0;
50 unsigned int m_n_active_nodes = 0;
51 double m_root_node_best_bound = -Inf;
52 double m_root_node_best_obj = +Inf;
54 std::optional<TreeNode> m_incumbent;
56 void build()
override;
57 void hook_before_optimize()
override;
58 void hook_optimize()
override;
59 void hook_after_optimize()
override;
60 void add(
const Var &t_var)
override;
61 void add(
const Ctr &t_ctr)
override;
62 void add(
const QCtr &t_ctr)
override;
63 void remove(
const Var &t_var)
override;
64 void remove(
const Ctr &t_ctr)
override;
65 void remove(
const QCtr &t_ctr)
override;
66 void update()
override;
67 void write(
const std::string &t_name)
override;
69 void detect_integer_objective();
70 void create_relaxations();
73 void analyze(
const TreeNode& t_node,
unsigned int t_relaxation_id,
bool* t_explore_children_flag,
bool* t_reoptimize_flag);
75 std::vector<TreeNode> create_child_nodes(
const TreeNode& t_node);
77 void set_as_incumbent(
const TreeNode& t_node);
78 [[nodiscard]]
bool gap_is_closed()
const;
81 bool is_valid(
const TreeNode& t_node)
const;
84 void log_after_termination();
87 void update_obj_sense()
override;
88 void update_obj()
override;
89 void update_rhs()
override;
90 void update_obj_constant()
override;
91 void update_mat_coeff(
const Ctr &t_ctr,
const Var &t_var)
override;
92 void update_ctr_type(
const Ctr &t_ctr)
override;
93 void update_ctr_rhs(
const Ctr &t_ctr)
override;
94 void update_var_type(
const Var &t_var)
override;
95 void update_var_lb(
const Var &t_var)
override;
96 void update_var_ub(
const Var &t_var)
override;
98 void update_var_obj(
const Var &t_var)
override;
99 void set_status(SolutionStatus t_status)
override;
100 void set_reason(SolutionReason t_reason)
override;
101 void set_best_bound(
double t_value)
override;
102 void set_best_obj(
double t_value)
override;
111 [[nodiscard]] std::string name()
const override {
return "Branch-and-Bound"; }
113 void solve(
TreeNode& t_node,
unsigned int t_relaxation_id)
const;
115 virtual void set_subtree_depth(
unsigned int t_depth) { m_steps.at(1) = t_depth; }
117 [[nodiscard]]
unsigned int subtree_depth()
const {
return m_steps.at(1); }
121 void submit_heuristic_solution(NodeInfoT* t_info);
123 void submit_lower_bound(
double t_lower_bound);
125 [[nodiscard]]
unsigned int n_created_nodes()
const {
return m_n_created_nodes; }
127 [[nodiscard]]
unsigned int n_solved_nodes()
const {
return m_n_solved_nodes; }
129 [[nodiscard]]
unsigned int n_active_nodes()
const {
return m_n_active_nodes; }
131 [[nodiscard]]
const Model& relaxation()
const {
return *m_relaxations.front(); }
133 [[nodiscard]]
Model& relaxation() {
return *m_relaxations.front(); }
135 [[nodiscard]]
double root_node_best_bound()
const {
return m_root_node_best_bound; }
137 [[nodiscard]]
double root_node_best_obj()
const {
return m_root_node_best_obj; }
143 [[nodiscard]]
bool has_incumbent()
const {
return m_incumbent.has_value(); }
145 const TreeNode& incumbent()
const {
return m_incumbent.value(); }
147 [[nodiscard]]
double get_var_primal(
const Var &t_var)
const override;
149 [[nodiscard]]
double get_var_reduced_cost(
const Var &t_var)
const override;
151 [[nodiscard]]
double get_var_ray(
const Var &t_var)
const override;
153 [[nodiscard]]
double get_ctr_dual(
const Ctr &t_ctr)
const override;
155 [[nodiscard]]
double get_ctr_farkas(
const Ctr &t_ctr)
const override;
157 [[nodiscard]]
unsigned int get_n_solutions()
const override;
159 [[nodiscard]]
unsigned int get_solution_index()
const override;
161 void set_solution_index(
unsigned int t_index)
override;
163 void terminate()
override;
170template<
class NodeInfoT>
172 for (
auto& relaxation : m_relaxations) {
173 relaxation->remove(t_ctr);
177template<
class NodeInfoT>
179 for (
auto& relaxation : m_relaxations) {
180 relaxation->add(t_ctr);
184template<
class NodeInfoT>
188 result = m_branching_rule->is_valid(t_node);
192template<
class NodeInfoT>
195 Optimizer::terminate();
199 const OptimizerFactory& t_factory) {
201 m_relaxation_optimizer_factory.reset(t_factory.clone());
207 return *m_relaxation_optimizer_factory;
210template<
class NodeInfoT>
212 const auto& src_model = parent();
213 const auto& objective = src_model.get_obj_expr();
215 if (!is_integer(objective.affine().constant(), Tolerance::Integer)) {
216 m_has_integer_objective =
false;
220 for (
const auto& [var, val] : objective.affine().linear()) {
221 if (src_model.get_var_type(var) == Continuous || !is_integer(val, Tolerance::Integer)) {
222 m_has_integer_objective =
false;
227 m_has_integer_objective =
true;
230template<
class NodeInfoT>
232 if (m_n_solved_nodes > 1) {
233 throw Exception(
"Reduced cost not available.");
235 return m_relaxations.front()->get_var_reduced_cost(t_var);
238template<
class NodeInfoT>
241 if (!get_param_logs()) {
246 m_callback->log_after_termination();
249 m_logger->log_after_termination();
253template<
class NodeInfoT>
255 m_logger->log_node_after_solve(t_node);
258template<
class NodeInfoT>
261 throw Exception(
"Solution index out of bounds.");
265template<
class NodeInfoT>
270template<
class NodeInfoT>
272 return m_incumbent.has_value();
275template<
class NodeInfoT>
277 for (
auto& relaxation : m_relaxations) {
278 relaxation->set_obj_expr(parent().get_obj_expr());
282template<
class NodeInfoT>
284 for (
auto& relaxation : m_relaxations) {
285 relaxation->set_rhs_expr(parent().get_rhs_expr());
289template<
class NodeInfoT>
291 for (
auto& relaxation : m_relaxations) {
292 relaxation->set_obj_const(parent().get_obj_expr().affine().constant());
296template<
class NodeInfoT>
298 for (
auto& relaxation : m_relaxations) {
299 relaxation->set_mat_coeff(t_ctr, t_var, parent().get_mat_coeff(t_ctr, t_var));
303template<
class NodeInfoT>
305 for (
auto& relaxation : m_relaxations) {
306 relaxation->set_ctr_type(t_ctr, parent().get_ctr_type(t_ctr));
310template<
class NodeInfoT>
312 for (
auto& relaxation : m_relaxations) {
313 relaxation->set_ctr_rhs(t_ctr, parent().get_ctr_rhs(t_ctr));
317template<
class NodeInfoT>
319 for (
auto& relaxation : m_relaxations) {
320 relaxation->set_var_type(t_var, parent().get_var_type(t_var));
324template<
class NodeInfoT>
326 for (
auto& relaxation : m_relaxations) {
327 relaxation->set_var_lb(t_var, parent().get_var_lb(t_var));
331template<
class NodeInfoT>
333 for (
auto& relaxation : m_relaxations) {
334 relaxation->set_var_ub(t_var, parent().get_var_ub(t_var));
338template<
class NodeInfoT>
340 for (
auto& relaxation : m_relaxations) {
341 relaxation->set_var_obj(t_var, parent().get_var_obj(t_var));
345template<
class NodeInfoT>
347 throw Exception(
"Not implemented");
350template<
class NodeInfoT>
352 if (m_n_solved_nodes > 1) {
353 throw Exception(
"Accessing Farkas certificate for MIP is not available after the root node.");
355 return m_relaxations.front()->get_ctr_farkas(t_ctr);
358template<
class NodeInfoT>
360 if (m_n_solved_nodes > 1) {
361 throw Exception(
"Accessing dual values for MIP is not available after the root node.");
363 return m_relaxations.front()->get_ctr_dual(t_ctr);
366template<
class NodeInfoT>
368 if (m_n_solved_nodes > 1) {
369 throw Exception(
"Ray not available.");
371 return m_relaxations.front()->get_var_ray(t_var);
374template<
class NodeInfoT>
377 if (!m_incumbent.has_value()){
378 throw Exception(
"Trying to access primal values while no incumbent was found.");
381 return m_incumbent->info().primal_solution().get(t_var);
384template<
class NodeInfoT>
386 if (t_lower_bound > get_best_obj()) {
388 set_reason(NotSpecified);
392 if (t_lower_bound > get_best_bound()) {
393 set_best_bound(t_lower_bound);
398template<
class NodeInfoT>
401 auto t_node = Node<NodeInfoT>::create_detached_node(t_info);
403 if (t_node.info().objective_value() < get_best_obj()) {
406 set_as_incumbent(t_node);
418template<
class NodeInfoT>
426 return m_callback->operator()(
this, t_event, t_node, m_relaxations[t_relaxation_id].get());
429template<
class NodeInfoT>
431 m_callback->add_callback(t_cb);
434template<
class NodeInfoT>
439template<
class NodeInfoT>
441 const OptimizerFactory& t_node_optimizer,
442 const BranchingRuleFactory<NodeInfoT>& t_branching_rule_factory,
443 const NodeSelectionRuleFactory<NodeInfoT>& t_node_selection_rule_factory,
444 AbstractBranchAndBoundCallbackI<NodeInfoT>* t_callback,
445 const Logs::BranchAndBound::Factory<NodeInfoT>& t_logger_factory)
446 : Algorithm(t_model),
447 m_relaxation_optimizer_factory(t_node_optimizer.clone()),
448 m_branching_rule(t_branching_rule_factory(*this)),
449 m_node_selection_rule(t_node_selection_rule_factory(*this)),
450 m_callback(t_callback),
451 m_logger(t_logger_factory.operator()(*this)) {
453 create_relaxations();
457template<
class NodeInfoT>
460 const auto &original_model = parent();
462 m_relaxations.clear();
463 m_relaxations.reserve(m_n_threads);
465 m_node_updators.clear();
466 m_node_updators.reserve(m_n_threads);
468 for (
unsigned int i = 0 ; i < m_n_threads ; ++i) {
469 auto* relaxation = original_model.clone();
470 relaxation->use(*m_relaxation_optimizer_factory);
471 m_relaxations.emplace_back(relaxation);
472 m_node_updators.emplace_back(
dynamic_cast<NodeUpdator<NodeInfoT>*
>(NodeInfoT::create_updator(parent(), *relaxation)));
477template<
class NodeInfoT>
480 auto root_node = Node<NodeInfoT>::create_root_node();
481 assert(root_node.id() == 0);
488template<
class NodeInfoT>
490 Algorithm::hook_before_optimize();
493 set_best_bound(std::max(-Inf, get_param_best_obj_stop()));
494 set_best_obj(std::min(+Inf, get_param_best_bound_stop()));
497 detect_integer_objective();
499 m_n_created_nodes = 0;
500 m_n_solved_nodes = 0;
501 m_n_active_nodes = 0;
503 m_root_node_best_bound = -Inf;
504 m_root_node_best_obj = Inf;
506 m_branching_rule->initialize();
507 for (
auto& node_updator : m_node_updators) {
508 node_updator->initialize();
510 m_callback->initialize(
this);
512 m_logger->initialize();
515template<
class NodeInfoT>
518 auto root_node = create_root_node();
520 SetOfActiveNodes active_nodes;
522 explore(root_node, 0, active_nodes, 0);
524 if (active_nodes.empty()) {
525 set_best_bound(get_best_obj());
528 m_node_updators[0]->clear();
530 if (get_status() == Fail) {
534 if (!m_incumbent.has_value()) {
536 if (is_pos_inf(get_best_obj())) {
537 set_status(Infeasible);
541 if (is_neg_inf(get_best_obj())) {
542 set_status(Unbounded);
548 set_status(Feasible);
550 if (gap_is_closed()) {
558template<
class NodeInfoT>
561 Optimizer::hook_after_optimize();
563 log_after_termination();
567template<
class NodeInfoT>
570 unsigned int t_relaxation_id,
571 SetOfActiveNodes & t_active_nodes,
572 unsigned int t_step) {
574 if (is_terminated() || gap_is_closed()) {
return; }
576 bool reoptimize_flag;
577 bool explore_children_flag;
581 solve(t_node, t_relaxation_id);
583 analyze(t_node, t_relaxation_id, &explore_children_flag, &reoptimize_flag);
585 log_node_after_solve(t_node);
587 }
while (reoptimize_flag);
592 if (is_terminated() || gap_is_closed()) {
return; }
594 if (!explore_children_flag) {
return; }
596 t_active_nodes.emplace(t_node);
598 const unsigned int max_levels = m_steps.at(t_step);
600 for (
unsigned int level = 0 ; level < max_levels ; ++level) {
602 prune_nodes_by_bound(t_active_nodes);
605 update_lower_bound(t_active_nodes);
606 m_n_active_nodes = t_active_nodes.size();
609 if (t_active_nodes.empty()) {
break; }
611 if (is_terminated() || gap_is_closed()) {
break; }
613 auto selected_node = select_node_for_branching(t_active_nodes);
615 auto children = create_child_nodes(selected_node);
617 const unsigned int n_children = children.size();
619 std::vector<SetOfActiveNodes> active_nodes(n_children);
621 if (m_n_threads > 1 && t_step == 0) {
623 std::vector<std::vector<unsigned int>> to_explore(m_n_threads);
625 for (
unsigned int q = 0 ; q < n_children ; ++q) {
626 const unsigned int relaxation_id = q % m_n_threads;
627 to_explore[relaxation_id].emplace_back(q);
630#pragma omp parallel for num_threads(m_n_threads)
631 for (
unsigned int k = 0 ; k < m_n_threads ; ++k) {
632 for (
unsigned int j = 0, n_tasks = to_explore[k].size() ; j < n_tasks ; ++j) {
633 const unsigned int q = to_explore[k][j];
634 explore(children[q], k, active_nodes[q], t_step + 1);
640 for (
unsigned int q = 0 ; q < n_children ; ++q) {
641 explore(children[q], t_relaxation_id, active_nodes[q], t_step + 1);
646 for (
unsigned int q = 0 ; q < n_children ; ++q) {
647 backtrack(t_active_nodes, active_nodes[q]);
654template<
class NodeInfoT>
656 unsigned int t_relaxation_id)
const {
658 auto& node_updator = *m_node_updators[t_relaxation_id];
659 auto& relaxation = *m_relaxations[t_relaxation_id];
661 relaxation.optimizer().set_param_best_bound_stop(std::min(get_best_obj(), get_param_best_bound_stop()));
662 relaxation.optimizer().set_param_time_limit(get_remaining_time());
664 node_updator.prepare(t_node);
676 relaxation.optimize();
678 t_node.info().save(parent(), relaxation);
680 m_branching_rule->on_node_solved(t_node);
684template<
class NodeInfoT>
687 *t_explore_children_flag =
false;
688 *t_reoptimize_flag =
false;
690 if (get_best_bound() > get_best_obj()) {
692 set_reason(NotSpecified);
697 const auto& status = t_node.info().status();
698 const auto& reason = t_node.info().reason();
700 if (get_remaining_time() == 0 || reason == TimeLimit) {
701 set_reason(TimeLimit);
706 if (status == Unbounded) {
713 if (status == Infeasible || status == InfOrUnbnd) {
715 if (t_node.level() == 0) {
719 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
723 if (status == Feasible && reason == ObjLimit) {
724 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
728 if (status == Fail || status == Loaded) {
731 set_reason(NotSpecified);
733 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
737 if (t_node.level() == 0) {
738 m_root_node_best_bound = t_node.info().objective_value();
741 if (t_node.info().objective_value() < get_best_obj()) {
743 if (is_valid(t_node)) {
745 auto side_effects = call_callbacks(IncumbentSolution, t_node, t_relaxation_id);
747 if (side_effects.n_added_user_cuts > 0 || side_effects.n_added_lazy_cuts > 0) {
748 *t_reoptimize_flag =
true;
752 set_as_incumbent(t_node);
759 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
764 auto side_effects = call_callbacks(InvalidSolution, t_node, t_relaxation_id);
766 if (t_node.level() == 0) {
767 m_root_node_best_obj = get_best_obj();
770 if (side_effects.n_added_lazy_cuts > 0 || side_effects.n_added_user_cuts > 0 || side_effects.n_added_local_variable_branching > 0) {
771 *t_reoptimize_flag =
true;
775 *t_explore_children_flag =
true;
779template<
class NodeInfoT>
782 m_incumbent = t_node;
783 set_best_obj(t_node.info().objective_value());
784 set_status(Feasible);
787template<
class NodeInfoT>
790 if (t_active_nodes.empty()) {
return; }
792 auto& lowest_node = *t_active_nodes.by_objective_value().begin();
793 const double raw_lower_bound = lowest_node.info().objective_value();
794 const double lower_bound = m_has_integer_objective && !is_integer(raw_lower_bound,
Tolerance::Integer) ? std::ceil(raw_lower_bound) : raw_lower_bound;
795 if (lower_bound > get_best_bound()) {
796 set_best_bound(lower_bound);
801template<
class NodeInfoT>
804 const double upper_bound = get_best_obj();
806 auto it = t_active_nodes.by_objective_value().begin();
807 auto end = t_active_nodes.by_objective_value().end();
811 const auto& node = *it;
812 const double raw_lower_bound = node.info().objective_value();
813 const double lower_bound = m_has_integer_objective && !is_integer(raw_lower_bound,
Tolerance::Integer) ? std::ceil(raw_lower_bound) : raw_lower_bound;
815 if (lower_bound >= upper_bound - get_tol_mip_absolute_gap()) {
816 it = t_active_nodes.erase(it);
817 end = t_active_nodes.by_objective_value().end();
826template<
class NodeInfoT>
829 auto iterator = m_node_selection_rule->operator()(t_active_nodes);
830 auto result = *iterator;
831 t_active_nodes.erase(iterator);
835template<
class NodeInfoT>
837 m_relaxations.front()->write(t_name);
840template<
class NodeInfoT>
843 auto children_info = m_branching_rule->create_child_nodes(t_node);
845 std::vector<Node<NodeInfoT>> result;
846 result.reserve(children_info.size());
848 for (
auto* ptr_to_info : children_info) {
849 result.emplace_back(ptr_to_info, m_n_created_nodes++, t_node);
852 m_branching_rule->on_nodes_have_been_created();
857template<
class NodeInfoT>
859 return get_relative_gap() <= get_tol_mip_relative_gap()
860 || get_absolute_gap() <= get_tol_mip_absolute_gap();
863template<
class NodeInfoT>
865 SetOfActiveNodes &t_sub_active_nodes) {
867 t_actives_nodes.merge(std::move(t_sub_active_nodes));
871template<
class NodeInfoT>
873 for (
auto& relaxation : m_relaxations) {
874 relaxation->update();
878template<
class NodeInfoT>
880 for (
auto& relaxation : m_relaxations) {
881 relaxation->remove(t_ctr);
885template<
class NodeInfoT>
887 for (
auto& relaxation : m_relaxations) {
888 relaxation->remove(t_var);
892template<
class NodeInfoT>
894 for (
auto& relaxation : m_relaxations) {
895 relaxation->add(t_ctr);
899template<
class NodeInfoT>
901 for (
auto& relaxation : m_relaxations) {
902 relaxation->add(t_var);
906template<
class NodeInfoT>
909 Algorithm::set_best_obj(t_value);
912template<
class NodeInfoT>
915 Algorithm::set_best_bound(t_value);
918template<
class NodeInfoT>
921 Algorithm::set_reason(t_reason);
924template<
class NodeInfoT>
927 Algorithm::set_status(t_status);