Loading...
Searching...
No Matches
Optimizers_BranchAndBound.h
1//
2// Created by henri on 13/03/23.
3//
4
5#ifndef IDOL_OPTIMIZERS_BRANCHANDBOUND_H
6#define IDOL_OPTIMIZERS_BRANCHANDBOUND_H
7
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"
21
22#include <memory>
23#include <cassert>
24#include <fstream>
25
26#include "idol/general/utils/Queue.h"
27#include "idol/mixed-integer/optimizers/presolve/Presolve.h"
28
29namespace idol::Optimizers {
30 template<class NodeInfoT> class BranchAndBound;
31}
32
33template<class NodeInfoT>
34class idol::Optimizers::BranchAndBound : public Algorithm {
35 using TreeNode = Node<NodeInfoT>;
36 using SetOfActiveNodes = NodeSet<Node<NodeInfoT>>;
37
38 std::unique_ptr<OptimizerFactory> m_relaxation_optimizer_factory;
39
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;
47
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;
52
53 std::unique_ptr<AbstractBranchAndBoundCallbackI<NodeInfoT>> m_callback;
54
55 // Presolve
56 Presolve m_presolve;
57
58 // Cuts
59 CutPool m_user_cut_pool;
60
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;
68
69 Queue<TreeNode> m_incumbents;
70protected:
71 [[nodiscard]] const Model& working_model() const { return m_presolved_model ? *m_presolved_model : parent(); }
72
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;
85
86 void detect_integer_objective();
87 void create_relaxations();
88 Node<NodeInfoT> create_root_node();
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;
99
100 void log_node_after_solve(const Node<NodeInfoT>& t_node);
101 void log_after_termination();
102
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;
114
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;
120public:
121 explicit BranchAndBound(const Model& t_model,
122 const OptimizerFactory& t_node_optimizer,
123 const BranchingRuleFactory<NodeInfoT>& t_branching_rule_factory,
124 const NodeSelectionRuleFactory<NodeInfoT>& t_node_selection_rule_factory,
126 const Logs::BranchAndBound::Factory<NodeInfoT>& t_logger_factory);
127
128 [[nodiscard]] std::string name() const override { return "Branch-and-Bound"; }
129
130 void solve(TreeNode& t_node, unsigned int t_relaxation_id);
131
132 virtual void set_subtree_depth(unsigned int t_depth) { m_steps.at(1) = t_depth; }
133
134 [[nodiscard]] unsigned int subtree_depth() const { return m_steps.at(1); }
135
136 virtual void add_callback(BranchAndBoundCallback<NodeInfoT>* t_cb);
137
138 virtual void add_presolver(const Presolvers::AbstractPresolver& t_presolver);
139
140 void submit_heuristic_solution(NodeInfoT* t_info);
141
142 void submit_lower_bound(double t_lower_bound);
143
144 // Returns true if the cut was effectively added
145 bool add_lazy_cut(const TempCtr &t_cut);
146
147 // Returns true if the cut was effectively added
148 bool add_user_cut(const TempCtr &t_cut);
149
150 [[nodiscard]] unsigned int n_created_nodes() const { return m_n_created_nodes; }
151
152 [[nodiscard]] unsigned int n_solved_nodes() const { return m_n_solved_nodes; }
153
154 [[nodiscard]] unsigned int n_active_nodes() const { return m_n_active_nodes; }
155
156 [[nodiscard]] const Model& relaxation() const { return *m_relaxations.front(); }
157
158 [[nodiscard]] Model& relaxation() { return *m_relaxations.front(); }
159
160 [[nodiscard]] double root_node_best_bound() const { return m_root_node_best_bound; }
161
162 [[nodiscard]] double root_node_best_obj() const { return m_root_node_best_obj; }
163
164 BranchingRule<NodeInfoT>& branching_rule() { return *m_branching_rule; }
165
166 const BranchingRule<NodeInfoT>& branching_rule() const { return *m_branching_rule; }
167
168 [[nodiscard]] bool has_incumbent() const { return !m_incumbents.empty(); }
169
170 const TreeNode& incumbent() const { return m_incumbents.at(m_current_solution_index); }
171
172 [[nodiscard]] double get_var_primal(const Var &t_var) const override;
173
174 [[nodiscard]] double get_var_reduced_cost(const Var &t_var) const override;
175
176 [[nodiscard]] double get_var_ray(const Var &t_var) const override;
177
178 [[nodiscard]] double get_ctr_dual(const Ctr &t_ctr) const override;
179
180 [[nodiscard]] double get_ctr_farkas(const Ctr &t_ctr) const override;
181
182 [[nodiscard]] unsigned int get_n_solutions() const override;
183
184 [[nodiscard]] unsigned int get_solution_index() const override;
185
186 void set_solution_index(unsigned int t_index) override;
187
188 void terminate() override;
189
190 void set_relaxation_optimizer_factory(const OptimizerFactory& t_factory);
191
192 void set_root_node_info(const NodeInfoT& t_node_info);
193
194 [[nodiscard]] const OptimizerFactory& get_relaxation_optimizer_factory() const;
195
196 [[nodiscard]] double get_best_obj() const override;
197};
198
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);
203 }
204}
205
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);
210 }
211}
212
213template<class NodeInfoT>
214bool idol::Optimizers::BranchAndBound<NodeInfoT>::is_valid(const TreeNode &t_node) const {
215 bool result;
216#pragma omp critical
217 result = m_branching_rule->is_valid(t_node);
218 return result;
219}
220
221template<class NodeInfoT>
222void idol::Optimizers::BranchAndBound<NodeInfoT>::terminate() {
223#pragma omp critical
224 Optimizer::terminate();
225}
226
227template <class NodeInfoT> void idol::Optimizers::BranchAndBound<NodeInfoT>::set_relaxation_optimizer_factory(
228 const OptimizerFactory& t_factory) {
229
230 m_relaxation_optimizer_factory.reset(t_factory.clone());
231
232}
233
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());
237}
238
241 return *m_relaxation_optimizer_factory;
242}
243
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();
248 }
249 return m_incumbents.at(m_current_solution_index).info().best_obj();
250}
251
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();
256
257 if (!is_integer(objective.affine().constant(), get_tol_integer())) {
258 m_has_integer_objective = false;
259 return;
260 }
261
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;
265 return;
266 }
267 }
268
269 m_has_integer_objective = true;
270}
271
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.");
276 }
277 return m_relaxations.front()->get_var_reduced_cost(t_var);
278}
279
280template<class NodeInfoT>
281void idol::Optimizers::BranchAndBound<NodeInfoT>::log_after_termination() {
282
283 if (!get_param_logs()) {
284 return;
285 }
286
287 if (m_callback) {
288 m_callback->log_after_termination();
289 }
290
291 m_logger->log_after_termination();
292
293}
294
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);
298}
299
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.");
304 }
305 m_current_solution_index = t_index;
306}
307
308template<class NodeInfoT>
309unsigned int idol::Optimizers::BranchAndBound<NodeInfoT>::get_solution_index() const {
310 return 0;
311}
312
313template<class NodeInfoT>
314unsigned int idol::Optimizers::BranchAndBound<NodeInfoT>::get_n_solutions() const {
315 return m_incumbents.size();
316}
317
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());
322 }
323}
324
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());
329 }
330}
331
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());
336 }
337}
338
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));
343 }
344}
345
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));
350 }
351}
352
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));
357 }
358}
359
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));
364 }
365}
366
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));
371 }
372}
373
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));
378 }
379}
380
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));
385 }
386}
387
388template<class NodeInfoT>
389void idol::Optimizers::BranchAndBound<NodeInfoT>::update_obj_sense() {
390 throw Exception("Not implemented");
391}
392
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.");
397 }
398 return m_relaxations.front()->get_ctr_farkas(t_ctr);
399}
400
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.");
405 }
406 return m_relaxations.front()->get_ctr_dual(t_ctr);
407}
408
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.");
413 }
414 return m_relaxations.front()->get_var_ray(t_var);
415}
416
417template<class NodeInfoT>
418double idol::Optimizers::BranchAndBound<NodeInfoT>::get_var_primal(const Var &t_var) const {
419
420 if (!has_incumbent()){
421 throw Exception("Trying to access primal values while no incumbent was found.");
422 }
423
424 return incumbent().info().primal_solution().get(t_var);
425}
426
427template<class NodeInfoT>
428void idol::Optimizers::BranchAndBound<NodeInfoT>::submit_lower_bound(double t_lower_bound) {
429 if (t_lower_bound > get_best_obj()) {
430 set_status(Fail);
431 set_reason(NotSpecified);
432 terminate();
433 return;
434 }
435 if (t_lower_bound > get_best_bound()) {
436 set_best_bound(t_lower_bound);
437 // New best LB
438 }
439}
440
441template <class NodeInfoT>
442bool idol::Optimizers::BranchAndBound<NodeInfoT>::add_lazy_cut(const TempCtr& t_cut) {
443 // TODO: add to lazy constraint pool
444 m_relaxations.front()->add_ctr(t_cut);
445 return true;
446}
447
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());
451}
452
453template<class NodeInfoT>
454void idol::Optimizers::BranchAndBound<NodeInfoT>::submit_heuristic_solution(NodeInfoT* t_info) {
455
456 auto t_node = Node<NodeInfoT>::create_detached_node(t_info);
457
458 if (t_node.info().best_obj() > get_best_obj()) {
459 return;
460 }
461
462 const auto registry = call_callbacks(IncumbentSolution, t_node, -1);
463
464 if (registry.n_added_lazy_cuts > 0) {
465 return;
466 }
467
468 set_as_incumbent(t_node);
469 //log_node_after_solve(t_node);
470
471 //if (m_branching_rule->is_valid(t_node)) {
472 // New incumbent by submission
473 //}
474
475}
476
477template<class NodeInfoT>
479idol::Optimizers::BranchAndBound<NodeInfoT>::call_callbacks(CallbackEvent t_event, const Optimizers::BranchAndBound<NodeInfoT>::TreeNode &t_node, unsigned int t_relaxation_id) {
480
481 if (!m_callback) {
482 return {};
483 }
484
485 Model* relaxation = nullptr;
486 if (t_relaxation_id != -1) {
487 relaxation = m_relaxations[t_relaxation_id].get();
488 }
489
490 return m_callback->operator()(this, t_event, t_node, relaxation);
491}
492
493template<class NodeInfoT>
494void idol::Optimizers::BranchAndBound<NodeInfoT>::add_callback(BranchAndBoundCallback<NodeInfoT> *t_cb) {
495 m_callback->add_callback(t_cb);
496}
497
498template <class NodeInfoT>
499void idol::Optimizers::BranchAndBound<NodeInfoT>::add_presolver(const Presolvers::AbstractPresolver& t_presolver) {
500 m_presolve.add(t_presolver);
501}
502
503template<class NodeInfoT>
504void idol::Optimizers::BranchAndBound<NodeInfoT>::build() {
505
506}
507
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) {
522
523}
524
525template<class NodeInfoT>
526void idol::Optimizers::BranchAndBound<NodeInfoT>::create_relaxations() {
527
528 const auto &original_model = working_model();
529
530 m_relaxations.clear();
531 m_relaxations.reserve(m_n_threads);
532
533 m_node_updators.clear();
534 m_node_updators.reserve(m_n_threads);
535
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)));
541 }
542
543}
544
545template<class NodeInfoT>
546idol::Node<NodeInfoT> idol::Optimizers::BranchAndBound<NodeInfoT>::create_root_node() {
547
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();
553 } else {
554 throw Exception("The given node info class has no default constructor and no root node info has been given.");
555 }
556 auto root_node = Node<NodeInfoT>::create_root_node(node_info);
557 assert(root_node.id() == 0);
558 ++m_n_created_nodes;
559
560 return root_node;
561
562}
563
564template<class NodeInfoT>
565void idol::Optimizers::BranchAndBound<NodeInfoT>::hook_before_optimize() {
566 Algorithm::hook_before_optimize();
567
568 // Reset solution
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();
572
573 detect_integer_objective();
574
575 m_n_created_nodes = 0;
576 m_n_solved_nodes = 0;
577 m_n_active_nodes = 0;
578 m_current_solution_index = 0;
579
580 m_root_node_best_bound = -Inf;
581 m_root_node_best_obj = Inf;
582
583}
584
585template<class NodeInfoT>
586void idol::Optimizers::BranchAndBound<NodeInfoT>::hook_optimize() {
587
588 if (get_param_logs()) {
589 parent().print_statistics(std::cout);
590 }
591
592 if (!m_presolve.empty()) {
593 m_presolved_model.reset(working_model().clone());
594 m_presolve.execute(*m_presolved_model);
595 }
596
597 create_relaxations();
598
599 m_branching_rule->initialize();
600 for (auto& node_updator : m_node_updators) {
601 node_updator->initialize();
602 }
603 m_callback->initialize(this);
604
605 m_logger->initialize();
606
607 auto root_node = create_root_node();
608
609 SetOfActiveNodes active_nodes;
610
611 explore(root_node, 0, active_nodes, 0);
612
613 if (active_nodes.empty()) {
614 set_best_bound(get_best_obj());
615 }
616
617 m_node_updators[0]->clear();
618
619 if (get_status() == Fail) {
620 return;
621 }
622
623 if (!has_incumbent()) {
624
625 if (is_pos_inf(get_best_obj())) {
626 set_status(Infeasible);
627 return;
628 }
629
630 if (is_neg_inf(get_best_obj())) {
631 set_status(Unbounded);
632 return;
633 }
634
635 }
636
637 set_status(Feasible);
638
639 if (gap_is_closed()) {
640 set_status(Optimal);
641 set_reason(Proved);
642 return;
643 }
644
645}
646
647template<class NodeInfoT>
648void idol::Optimizers::BranchAndBound<NodeInfoT>::hook_after_optimize() {
649
650 Optimizer::hook_after_optimize();
651
652 log_after_termination();
653
654}
655
656template<class NodeInfoT>
657void idol::Optimizers::BranchAndBound<NodeInfoT>::explore(
658 TreeNode &t_node,
659 unsigned int t_relaxation_id,
660 SetOfActiveNodes & t_active_nodes,
661 unsigned int t_step) {
662
663 if (is_terminated() || gap_is_closed()) { return; }
664
665 bool reoptimize_flag;
666 bool explore_children_flag;
667
668 do {
669
670 solve(t_node, t_relaxation_id);
671
672 analyze(t_node, t_relaxation_id, &explore_children_flag, &reoptimize_flag);
673
674 log_node_after_solve(t_node);
675
676 } while (reoptimize_flag);
677
678#pragma omp atomic
679 ++m_n_solved_nodes;
680
681 if (is_terminated() || gap_is_closed()) { return; }
682
683 if (!explore_children_flag) { return; }
684
685 t_active_nodes.emplace(t_node);
686
687 const unsigned int max_levels = m_steps.at(t_step);
688
689 for (unsigned int level = 0 ; level < max_levels ; ++level) {
690
691 prune_nodes_by_bound(t_active_nodes);
692
693 if (t_step == 0) {
694 update_lower_bound(t_active_nodes);
695 m_n_active_nodes = t_active_nodes.size();
696 }
697
698 if (t_active_nodes.empty()) { break; }
699
700 if (is_terminated() || gap_is_closed()) { break; }
701
702 auto selected_node = select_node_for_branching(t_active_nodes);
703
704 auto children = create_child_nodes(selected_node);
705
706 const unsigned int n_children = children.size();
707
708 std::vector<SetOfActiveNodes> active_nodes(n_children);
709
710 if (m_n_threads > 1 && t_step == 0) {
711
712 std::vector<std::vector<unsigned int>> to_explore(m_n_threads);
713
714 for (unsigned int q = 0 ; q < n_children ; ++q) {
715 const unsigned int relaxation_id = q % m_n_threads;
716 to_explore[relaxation_id].emplace_back(q);
717 }
718
719#pragma omp parallel for num_threads(m_n_threads)
720 for (unsigned int k = 0 ; k < m_n_threads ; ++k) {
721 for (unsigned int j = 0, n_tasks = to_explore[k].size() ; j < n_tasks ; ++j) {
722 const unsigned int q = to_explore[k][j];
723 explore(children[q], k, active_nodes[q], t_step + 1);
724 }
725 }
726
727 } else {
728
729 for (unsigned int q = 0 ; q < n_children ; ++q) {
730 explore(children[q], t_relaxation_id, active_nodes[q], t_step + 1);
731 }
732
733 }
734
735 for (unsigned int q = 0 ; q < n_children ; ++q) {
736 backtrack(t_active_nodes, active_nodes[q]);
737 }
738
739 }
740
741}
742
743template<class NodeInfoT>
744void idol::Optimizers::BranchAndBound<NodeInfoT>::solve(TreeNode& t_node,
745 unsigned int t_relaxation_id) {
746
747 auto& node_updator = *m_node_updators[t_relaxation_id];
748 auto& relaxation = *m_relaxations[t_relaxation_id];
749
750 relaxation.optimizer().set_param_best_bound_stop(std::min(get_best_obj(), get_param_best_bound_stop()));
751 relaxation.optimizer().set_param_time_limit(get_remaining_time());
752
753 node_updator.prepare(t_node);
754
755 // TODO call callback "before node"
756
757 /*
758 for (const auto& var : working_model().vars()) {
759 const double lb = relaxation.get_var_lb(var);
760 const double ub = relaxation.get_var_ub(var);
761 if (lb > ub + Tolerance::Integer) {
762 std::cerr << "Inconsistent bounds for variable " << var << ": " << lb << " > " << ub << std::endl;
763 }
764 }
765 */
766
767 m_user_cut_pool.clean_up(relaxation);
768
769 relaxation.optimize();
770
771 t_node.info().save(working_model(), relaxation);
772
773 m_branching_rule->on_node_solved(t_node);
774
775}
776
777template<class NodeInfoT>
778void idol::Optimizers::BranchAndBound<NodeInfoT>::analyze(const BranchAndBound::TreeNode &t_node, unsigned int t_relaxation_id, bool* t_explore_children_flag, bool* t_reoptimize_flag) {
779
780 *t_explore_children_flag = false;
781 *t_reoptimize_flag = false;
782
783 if (get_best_bound() > get_best_obj()) {
784 set_status(Fail);
785 set_reason(NotSpecified);
786 terminate();
787 return;
788 }
789
790 const auto& status = t_node.info().status();
791 const auto& reason = t_node.info().reason();
792
793 if (get_remaining_time() == 0 || reason == TimeLimit) {
794 set_reason(TimeLimit);
795 terminate();
796 return;
797 }
798
799 if (status == Unbounded) {
800 set_reason(Proved);
801 set_best_obj(-Inf);
802 terminate();
803 return;
804 }
805
806 if (status == Infeasible || status == InfOrUnbnd) {
807
808 if (t_node.level() == 0) {
809 set_status(status);
810 }
811
812 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
813 return;
814 }
815
816 if (status == Feasible && reason == ObjLimit) {
817 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
818 return;
819 }
820
821 if (status == Fail || status == Loaded) {
822
823 set_status(Fail);
824 set_reason(NotSpecified);
825 terminate();
826 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
827 return;
828 }
829
830 if (t_node.level() == 0) {
831 m_root_node_best_bound = t_node.info().best_bound();
832 set_best_bound(std::max(m_root_node_best_bound, get_best_bound()));
833 }
834
835 if (t_node.info().best_bound() < get_best_obj()) {
836
837 if (is_valid(t_node)) {
838
839 auto side_effects = call_callbacks(IncumbentSolution, t_node, t_relaxation_id);
840
841 if (side_effects.n_added_user_cuts > 0 || side_effects.n_added_lazy_cuts > 0) {
842 *t_reoptimize_flag = true;
843 return;
844 }
845
846 set_as_incumbent(t_node);
847 return;
848
849 }
850
851 } else {
852
853 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
854 return;
855
856 }
857
858 const unsigned int recycled_user_cuts = m_user_cut_pool.recycle(t_node.info().primal_solution(), *m_relaxations[t_relaxation_id], get_tol_feasibility());
859 if (recycled_user_cuts > 0) {
860 *t_reoptimize_flag = true;
861 return;
862 }
863
864 auto side_effects = call_callbacks(InvalidSolution, t_node, t_relaxation_id);
865
866 if (t_node.level() == 0) {
867 m_root_node_best_obj = get_best_obj();
868 }
869
870 if (side_effects.n_added_lazy_cuts > 0 || side_effects.n_added_user_cuts > 0 || side_effects.n_added_local_variable_branching > 0) {
871 *t_reoptimize_flag = true;
872 return;
873 }
874
875 *t_explore_children_flag = true;
876
877}
878
879template<class NodeInfoT>
880void idol::Optimizers::BranchAndBound<NodeInfoT>::set_as_incumbent(const BranchAndBound::TreeNode &t_node) {
881#pragma omp critical
882 m_incumbents.push(t_node);
883 set_best_obj(t_node.info().best_obj());
884 set_status(Feasible);
885}
886
887template<class NodeInfoT>
888void idol::Optimizers::BranchAndBound<NodeInfoT>::update_lower_bound(const BranchAndBound::SetOfActiveNodes &t_active_nodes) {
889
890 if (t_active_nodes.empty()) { return; }
891
892 auto& lowest_node = *t_active_nodes.by_objective_value().begin();
893 const double raw_lower_bound = lowest_node.info().best_bound();
894 const double lower_bound = m_has_integer_objective ? std::ceil(raw_lower_bound - get_tol_integer()) : raw_lower_bound;
895 if (lower_bound > get_best_bound()) {
896 set_best_bound(lower_bound);
897 }
898
899}
900
901template<class NodeInfoT>
902void idol::Optimizers::BranchAndBound<NodeInfoT>::prune_nodes_by_bound(BranchAndBound::SetOfActiveNodes& t_active_nodes) {
903
904 const double upper_bound = get_best_obj();
905
906 auto it = t_active_nodes.by_objective_value().begin();
907 auto end = t_active_nodes.by_objective_value().end();
908
909 while (it != end) {
910
911 const auto& node = *it;
912 const double raw_lower_bound = node.info().best_bound();
913 const double lower_bound = m_has_integer_objective && !is_integer(raw_lower_bound, get_tol_integer()) ? std::ceil(raw_lower_bound) : raw_lower_bound;
914
915 if (lower_bound >= upper_bound - get_tol_mip_absolute_gap()) {
916 it = t_active_nodes.erase(it);
917 end = t_active_nodes.by_objective_value().end();
918 } else {
919 break;
920 }
921
922 }
923
924}
925
926template<class NodeInfoT>
927idol::Node<NodeInfoT>
928idol::Optimizers::BranchAndBound<NodeInfoT>::select_node_for_branching(BranchAndBound::SetOfActiveNodes &t_active_nodes) {
929 auto iterator = m_node_selection_rule->operator()(t_active_nodes);
930 auto result = *iterator;
931 t_active_nodes.erase(iterator);
932 return result;
933}
934
935template<class NodeInfoT>
936void idol::Optimizers::BranchAndBound<NodeInfoT>::write(const std::string &t_name) {
937 m_relaxations.front()->write(t_name);
938}
939
940template<class NodeInfoT>
941std::vector<idol::Node<NodeInfoT>> idol::Optimizers::BranchAndBound<NodeInfoT>::create_child_nodes(const BranchAndBound::TreeNode &t_node) {
942
943 auto children_info = m_branching_rule->create_child_nodes(t_node);
944
945 std::vector<Node<NodeInfoT>> result;
946 result.reserve(children_info.size());
947
948 for (auto* ptr_to_info : children_info) {
949 result.emplace_back(ptr_to_info, m_n_created_nodes++, t_node);
950 }
951
952 m_branching_rule->on_nodes_have_been_created();
953
954 return result;
955}
956
957template<class NodeInfoT>
958bool idol::Optimizers::BranchAndBound<NodeInfoT>::gap_is_closed() const {
959 return get_relative_gap() <= get_tol_mip_relative_gap()
960 || get_absolute_gap() <= get_tol_mip_absolute_gap();
961}
962
963template<class NodeInfoT>
964void idol::Optimizers::BranchAndBound<NodeInfoT>::backtrack(BranchAndBound::SetOfActiveNodes &t_actives_nodes,
965 SetOfActiveNodes &t_sub_active_nodes) {
966
967 t_actives_nodes.merge(std::move(t_sub_active_nodes));
968
969}
970
971template<class NodeInfoT>
972void idol::Optimizers::BranchAndBound<NodeInfoT>::update() {
973 for (auto& relaxation : m_relaxations) {
974 relaxation->update();
975 }
976}
977
978template<class NodeInfoT>
979void idol::Optimizers::BranchAndBound<NodeInfoT>::remove(const Ctr &t_ctr) {
980 for (auto& relaxation : m_relaxations) {
981 relaxation->remove(t_ctr);
982 }
983}
984
985template<class NodeInfoT>
986void idol::Optimizers::BranchAndBound<NodeInfoT>::remove(const Var &t_var) {
987 for (auto& relaxation : m_relaxations) {
988 relaxation->remove(t_var);
989 }
990}
991
992template<class NodeInfoT>
993void idol::Optimizers::BranchAndBound<NodeInfoT>::add(const Ctr &t_ctr) {
994 for (auto& relaxation : m_relaxations) {
995 relaxation->add(t_ctr);
996 }
997}
998
999template<class NodeInfoT>
1000void idol::Optimizers::BranchAndBound<NodeInfoT>::add(const Var &t_var) {
1001 for (auto& relaxation : m_relaxations) {
1002 relaxation->add(t_var);
1003 }
1004}
1005
1006template<class NodeInfoT>
1007void idol::Optimizers::BranchAndBound<NodeInfoT>::set_best_obj(double t_value) {
1008#pragma omp critical
1009 Algorithm::set_best_obj(t_value);
1010 //assert(get_best_bound() <= get_best_obj() + get_tol_mip_absolute_gap());
1011}
1012
1013template<class NodeInfoT>
1014void idol::Optimizers::BranchAndBound<NodeInfoT>::set_best_bound(double t_value) {
1015#pragma omp critical
1016 Algorithm::set_best_bound(t_value);
1017 //assert(get_best_bound() <= get_best_obj() + get_tol_mip_absolute_gap());
1018}
1019
1020template<class NodeInfoT>
1021void idol::Optimizers::BranchAndBound<NodeInfoT>::set_reason(idol::SolutionReason t_reason) {
1022#pragma omp critical
1023 Algorithm::set_reason(t_reason);
1024}
1025
1026template<class NodeInfoT>
1027void idol::Optimizers::BranchAndBound<NodeInfoT>::set_status(idol::SolutionStatus t_status) {
1028#pragma omp critical
1029 Algorithm::set_status(t_status);
1030}
1031
1032#endif //IDOL_OPTIMIZERS_BRANCHANDBOUND_H