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().objective_value();
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().objective_value() > 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 (!m_presolve.empty()) {
589 m_presolved_model.reset(working_model().clone());
590 m_presolve.execute(*m_presolved_model);
591 }
592
593 create_relaxations();
594
595 m_branching_rule->initialize();
596 for (auto& node_updator : m_node_updators) {
597 node_updator->initialize();
598 }
599 m_callback->initialize(this);
600
601 m_logger->initialize();
602
603 auto root_node = create_root_node();
604
605 SetOfActiveNodes active_nodes;
606
607 explore(root_node, 0, active_nodes, 0);
608
609 if (active_nodes.empty()) {
610 set_best_bound(get_best_obj());
611 }
612
613 m_node_updators[0]->clear();
614
615 if (get_status() == Fail) {
616 return;
617 }
618
619 if (!has_incumbent()) {
620
621 if (is_pos_inf(get_best_obj())) {
622 set_status(Infeasible);
623 return;
624 }
625
626 if (is_neg_inf(get_best_obj())) {
627 set_status(Unbounded);
628 return;
629 }
630
631 }
632
633 set_status(Feasible);
634
635 if (gap_is_closed()) {
636 set_status(Optimal);
637 set_reason(Proved);
638 return;
639 }
640
641}
642
643template<class NodeInfoT>
644void idol::Optimizers::BranchAndBound<NodeInfoT>::hook_after_optimize() {
645
646 Optimizer::hook_after_optimize();
647
648 log_after_termination();
649
650}
651
652template<class NodeInfoT>
653void idol::Optimizers::BranchAndBound<NodeInfoT>::explore(
654 TreeNode &t_node,
655 unsigned int t_relaxation_id,
656 SetOfActiveNodes & t_active_nodes,
657 unsigned int t_step) {
658
659 if (is_terminated() || gap_is_closed()) { return; }
660
661 bool reoptimize_flag;
662 bool explore_children_flag;
663
664 do {
665
666 solve(t_node, t_relaxation_id);
667
668 analyze(t_node, t_relaxation_id, &explore_children_flag, &reoptimize_flag);
669
670 log_node_after_solve(t_node);
671
672 } while (reoptimize_flag);
673
674#pragma omp atomic
675 ++m_n_solved_nodes;
676
677 if (is_terminated() || gap_is_closed()) { return; }
678
679 if (!explore_children_flag) { return; }
680
681 t_active_nodes.emplace(t_node);
682
683 const unsigned int max_levels = m_steps.at(t_step);
684
685 for (unsigned int level = 0 ; level < max_levels ; ++level) {
686
687 prune_nodes_by_bound(t_active_nodes);
688
689 if (t_step == 0) {
690 update_lower_bound(t_active_nodes);
691 m_n_active_nodes = t_active_nodes.size();
692 }
693
694 if (t_active_nodes.empty()) { break; }
695
696 if (is_terminated() || gap_is_closed()) { break; }
697
698 auto selected_node = select_node_for_branching(t_active_nodes);
699
700 auto children = create_child_nodes(selected_node);
701
702 const unsigned int n_children = children.size();
703
704 std::vector<SetOfActiveNodes> active_nodes(n_children);
705
706 if (m_n_threads > 1 && t_step == 0) {
707
708 std::vector<std::vector<unsigned int>> to_explore(m_n_threads);
709
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);
713 }
714
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);
720 }
721 }
722
723 } else {
724
725 for (unsigned int q = 0 ; q < n_children ; ++q) {
726 explore(children[q], t_relaxation_id, active_nodes[q], t_step + 1);
727 }
728
729 }
730
731 for (unsigned int q = 0 ; q < n_children ; ++q) {
732 backtrack(t_active_nodes, active_nodes[q]);
733 }
734
735 }
736
737}
738
739template<class NodeInfoT>
740void idol::Optimizers::BranchAndBound<NodeInfoT>::solve(TreeNode& t_node,
741 unsigned int t_relaxation_id) {
742
743 auto& node_updator = *m_node_updators[t_relaxation_id];
744 auto& relaxation = *m_relaxations[t_relaxation_id];
745
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());
748
749 node_updator.prepare(t_node);
750
751 // TODO call callback "before node"
752
753 /*
754 for (const auto& var : working_model().vars()) {
755 const double lb = relaxation.get_var_lb(var);
756 const double ub = relaxation.get_var_ub(var);
757 if (lb > ub + Tolerance::Integer) {
758 std::cerr << "Inconsistent bounds for variable " << var << ": " << lb << " > " << ub << std::endl;
759 }
760 }
761 */
762
763 m_user_cut_pool.clean_up(relaxation);
764
765 relaxation.optimize();
766
767 t_node.info().save(working_model(), relaxation);
768
769 m_branching_rule->on_node_solved(t_node);
770
771}
772
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) {
775
776 *t_explore_children_flag = false;
777 *t_reoptimize_flag = false;
778
779 if (get_best_bound() > get_best_obj()) {
780 set_status(Fail);
781 set_reason(NotSpecified);
782 terminate();
783 return;
784 }
785
786 const auto& status = t_node.info().status();
787 const auto& reason = t_node.info().reason();
788
789 if (get_remaining_time() == 0 || reason == TimeLimit) {
790 set_reason(TimeLimit);
791 terminate();
792 return;
793 }
794
795 if (status == Unbounded) {
796 set_reason(Proved);
797 set_best_obj(-Inf);
798 terminate();
799 return;
800 }
801
802 if (status == Infeasible || status == InfOrUnbnd) {
803
804 if (t_node.level() == 0) {
805 set_status(status);
806 }
807
808 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
809 return;
810 }
811
812 if (status == Feasible && reason == ObjLimit) {
813 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
814 return;
815 }
816
817 if (status == Fail || status == Loaded) {
818
819 set_status(Fail);
820 set_reason(NotSpecified);
821 terminate();
822 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
823 return;
824 }
825
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()));
829 }
830
831 if (t_node.info().objective_value() < get_best_obj()) {
832
833 if (is_valid(t_node)) {
834
835 auto side_effects = call_callbacks(IncumbentSolution, t_node, t_relaxation_id);
836
837 if (side_effects.n_added_user_cuts > 0 || side_effects.n_added_lazy_cuts > 0) {
838 *t_reoptimize_flag = true;
839 return;
840 }
841
842 set_as_incumbent(t_node);
843 return;
844
845 }
846
847 } else {
848
849 call_callbacks(PrunedSolution, t_node, t_relaxation_id);
850 return;
851
852 }
853
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;
857 return;
858 }
859
860 auto side_effects = call_callbacks(InvalidSolution, t_node, t_relaxation_id);
861
862 if (t_node.level() == 0) {
863 m_root_node_best_obj = get_best_obj();
864 }
865
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;
868 return;
869 }
870
871 *t_explore_children_flag = true;
872
873}
874
875template<class NodeInfoT>
876void idol::Optimizers::BranchAndBound<NodeInfoT>::set_as_incumbent(const BranchAndBound::TreeNode &t_node) {
877#pragma omp critical
878 m_incumbents.push(t_node);
879 set_best_obj(t_node.info().objective_value());
880 set_status(Feasible);
881}
882
883template<class NodeInfoT>
884void idol::Optimizers::BranchAndBound<NodeInfoT>::update_lower_bound(const BranchAndBound::SetOfActiveNodes &t_active_nodes) {
885
886 if (t_active_nodes.empty()) { return; }
887
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);
893 }
894
895}
896
897template<class NodeInfoT>
898void idol::Optimizers::BranchAndBound<NodeInfoT>::prune_nodes_by_bound(BranchAndBound::SetOfActiveNodes& t_active_nodes) {
899
900 const double upper_bound = get_best_obj();
901
902 auto it = t_active_nodes.by_objective_value().begin();
903 auto end = t_active_nodes.by_objective_value().end();
904
905 while (it != end) {
906
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;
910
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();
914 } else {
915 break;
916 }
917
918 }
919
920}
921
922template<class NodeInfoT>
923idol::Node<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);
928 return result;
929}
930
931template<class NodeInfoT>
932void idol::Optimizers::BranchAndBound<NodeInfoT>::write(const std::string &t_name) {
933 m_relaxations.front()->write(t_name);
934}
935
936template<class NodeInfoT>
937std::vector<idol::Node<NodeInfoT>> idol::Optimizers::BranchAndBound<NodeInfoT>::create_child_nodes(const BranchAndBound::TreeNode &t_node) {
938
939 auto children_info = m_branching_rule->create_child_nodes(t_node);
940
941 std::vector<Node<NodeInfoT>> result;
942 result.reserve(children_info.size());
943
944 for (auto* ptr_to_info : children_info) {
945 result.emplace_back(ptr_to_info, m_n_created_nodes++, t_node);
946 }
947
948 m_branching_rule->on_nodes_have_been_created();
949
950 return result;
951}
952
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();
957}
958
959template<class NodeInfoT>
960void idol::Optimizers::BranchAndBound<NodeInfoT>::backtrack(BranchAndBound::SetOfActiveNodes &t_actives_nodes,
961 SetOfActiveNodes &t_sub_active_nodes) {
962
963 t_actives_nodes.merge(std::move(t_sub_active_nodes));
964
965}
966
967template<class NodeInfoT>
968void idol::Optimizers::BranchAndBound<NodeInfoT>::update() {
969 for (auto& relaxation : m_relaxations) {
970 relaxation->update();
971 }
972}
973
974template<class NodeInfoT>
975void idol::Optimizers::BranchAndBound<NodeInfoT>::remove(const Ctr &t_ctr) {
976 for (auto& relaxation : m_relaxations) {
977 relaxation->remove(t_ctr);
978 }
979}
980
981template<class NodeInfoT>
982void idol::Optimizers::BranchAndBound<NodeInfoT>::remove(const Var &t_var) {
983 for (auto& relaxation : m_relaxations) {
984 relaxation->remove(t_var);
985 }
986}
987
988template<class NodeInfoT>
989void idol::Optimizers::BranchAndBound<NodeInfoT>::add(const Ctr &t_ctr) {
990 for (auto& relaxation : m_relaxations) {
991 relaxation->add(t_ctr);
992 }
993}
994
995template<class NodeInfoT>
996void idol::Optimizers::BranchAndBound<NodeInfoT>::add(const Var &t_var) {
997 for (auto& relaxation : m_relaxations) {
998 relaxation->add(t_var);
999 }
1000}
1001
1002template<class NodeInfoT>
1003void idol::Optimizers::BranchAndBound<NodeInfoT>::set_best_obj(double t_value) {
1004#pragma omp critical
1005 Algorithm::set_best_obj(t_value);
1006 assert(get_best_bound() <= get_best_obj() + get_tol_mip_absolute_gap());
1007}
1008
1009template<class NodeInfoT>
1010void idol::Optimizers::BranchAndBound<NodeInfoT>::set_best_bound(double t_value) {
1011#pragma omp critical
1012 Algorithm::set_best_bound(t_value);
1013 assert(get_best_bound() <= get_best_obj() + get_tol_mip_absolute_gap());
1014}
1015
1016template<class NodeInfoT>
1017void idol::Optimizers::BranchAndBound<NodeInfoT>::set_reason(idol::SolutionReason t_reason) {
1018#pragma omp critical
1019 Algorithm::set_reason(t_reason);
1020}
1021
1022template<class NodeInfoT>
1023void idol::Optimizers::BranchAndBound<NodeInfoT>::set_status(idol::SolutionStatus t_status) {
1024#pragma omp critical
1025 Algorithm::set_status(t_status);
1026}
1027
1028#endif //IDOL_OPTIMIZERS_BRANCHANDBOUND_H