Loading...
Searching...
No Matches
Info.h
1//
2// Created by henri on 23.11.23.
3//
4
5#ifndef IDOL_BRANCHANDBOUND_INFO_H
6#define IDOL_BRANCHANDBOUND_INFO_H
7
8#include "Factory.h"
9#include "idol/general/optimizers/logs.h"
10
11namespace idol::Logs::BranchAndBound {
12 template<class NodeInfoT> class Info;
13}
14
15template<class NodeInfoT = idol::DefaultNodeInfo>
16class idol::Logs::BranchAndBound::Info : public Factory<NodeInfoT> {
17
18 static constexpr auto double_precision = 5;
19 static constexpr auto n_sections = 3;
20
21 static constexpr auto log_width_explored = 6;
22 static constexpr auto log_width_open = 6;
23 static constexpr auto log_width_total_time = 12;
24
25 static constexpr auto log_width_current_node_depth = 6;
26 static constexpr auto log_width_problem_best_bound = 10;
27 static constexpr auto log_width_problem_best_obj = 10;
28 static constexpr auto log_width_problem_rel_gap = 10;
29 static constexpr auto log_width_problem_abs_gap = 10;
30
31 static constexpr auto log_width_current_node_status = 12;
32 static constexpr auto log_width_current_node_reason = 12;
33 static constexpr auto log_width_current_node_obj = 10;
34
35 static constexpr auto log_section_width_algorithm = (log_width_explored + log_width_open + log_width_total_time);
36 static constexpr auto log_section_width_problem = (log_width_problem_best_bound + log_width_problem_best_obj + log_width_problem_rel_gap + log_width_problem_abs_gap);
37 static constexpr auto current_node_space = (log_width_current_node_status + log_width_current_node_reason + log_width_current_node_obj + log_width_current_node_depth);
38 static constexpr auto table_space = log_section_width_algorithm + log_section_width_problem + current_node_space + n_sections * 3 + 1;
39
40
41 std::optional<double> m_log_frequency_in_seconds;
42 std::optional<double> m_node_logs;
43public:
44 class Strategy : public Factory<NodeInfoT>::Strategy {
45 double m_last_log_timestamp = 0;
46 double m_frequency_in_seconds;
47 bool m_node_logs;
48 bool m_header_has_been_printed = false;
49 bool m_root_node_has_been_printed = false;
50 protected:
51 void log_header();
52 public:
53
54 Strategy(Optimizers::BranchAndBound<NodeInfoT>& t_parent, double t_frequency_in_seconds, bool t_node_logs);
55
56 void initialize() override;
57
58 void log_node_after_solve(const Node <NodeInfoT> &t_node) override;
59
60 void log_root_node(const Node<NodeInfoT>& t_node);
61
62 void log_after_termination() override;
63 };
64
65 Strategy *operator()(Optimizers::BranchAndBound<NodeInfoT>& t_parent) const override;
66
67 Factory<NodeInfoT> *clone() const override;
68
69 Info& with_frequency_in_seconds(double t_frequency);
70
71 Info& with_node_logs(bool t_value);
72};
73
74template<class NodeInfoT>
75idol::Logs::BranchAndBound::Info<NodeInfoT> &idol::Logs::BranchAndBound::Info<NodeInfoT>::with_node_logs(bool t_value) {
76
77 if (m_node_logs.has_value()) {
78 throw Exception("Node logs have already been configured.");
79 }
80
81 m_node_logs = t_value;
82
83 return *this;
84}
85
86template<class NodeInfoT>
87idol::Logs::BranchAndBound::Info<NodeInfoT> &idol::Logs::BranchAndBound::Info<NodeInfoT>::with_frequency_in_seconds(double t_frequency) {
88
89 if (m_log_frequency_in_seconds.has_value()) {
90 throw Exception("Log frequency has already been configured.");
91 }
92
93 m_log_frequency_in_seconds = t_frequency;
94
95 return *this;
96}
97
98template<class NodeInfoT>
99typename idol::Logs::BranchAndBound::Info<NodeInfoT>::Strategy *idol::Logs::BranchAndBound::Info<NodeInfoT>::operator()(Optimizers::BranchAndBound<NodeInfoT>& t_parent) const {
100 return new Strategy(t_parent,
101 m_log_frequency_in_seconds.has_value() ? m_log_frequency_in_seconds.value() : 5,
102 m_node_logs.has_value() ? m_node_logs.value() : false);
103}
104
105template<class NodeInfoT>
106idol::Logs::BranchAndBound::Factory<NodeInfoT> *idol::Logs::BranchAndBound::Info<NodeInfoT>::clone() const {
107 return new Info<NodeInfoT>(*this);
108}
109
110template<class NodeInfoT>
111idol::Logs::BranchAndBound::Info<NodeInfoT>::Strategy::Strategy(Optimizers::BranchAndBound<NodeInfoT> &t_parent, double t_frequency_in_seconds, bool t_node_logs)
112 : Factory<NodeInfoT>::Strategy(t_parent),
113 m_frequency_in_seconds(t_frequency_in_seconds),
114 m_node_logs(t_node_logs) {
115
116}
117
118template<class NodeInfoT>
119void idol::Logs::BranchAndBound::Info<NodeInfoT>::Strategy::initialize() {
120
121 m_last_log_timestamp = 0;
122 m_header_has_been_printed = false;
123 m_root_node_has_been_printed = false;
124
125 if (!this->parent().get_param_logs()) {
126 return;
127 }
128
129 Factory<NodeInfoT>::Strategy::initialize();
130
131 std::cout << "Solving root node with " << this->parent().relaxation().optimizer().name() << "...\n" << std::endl;
132
133 auto& branch_and_bound = this->parent();
134 branch_and_bound.relaxation().optimizer().set_param_logs(true);
135}
136
137template<class NodeInfoT>
138void idol::Logs::BranchAndBound::Info<NodeInfoT>::Strategy::log_node_after_solve(const Node <NodeInfoT> &t_node) {
139
140 if (!this->parent().get_param_logs()) {
141 return;
142 }
143
144 Factory<NodeInfoT>::Strategy::Strategy::log_node_after_solve(t_node);
145
146 const auto& parent = this->parent();
147
148 const double total_time = parent.time().count();
149
150 if (!m_root_node_has_been_printed && t_node.id() == 0) {
151 if (t_node.id() == 0) {
152 log_root_node(t_node);
153 }
154 return;
155 }
156
157 if (t_node.id() > 0 && total_time - m_last_log_timestamp < m_frequency_in_seconds) {
158 return;
159 }
160
161 log_header();
162
163 m_last_log_timestamp = total_time;
164
165 std::cout << " | ";
166 std::cout << std::setw(log_width_explored) << parent.n_solved_nodes();
167 std::cout << std::setw(log_width_open) << parent.n_active_nodes();
168 std::cout << std::setw(log_width_total_time) << parent.time().count();
169
170 // Problem
171 std::cout << " | ";
172 std::cout << std::setw(log_width_problem_best_bound) << pretty_double(parent.get_best_bound(), double_precision);
173 std::cout << std::setw(log_width_problem_best_obj) << pretty_double(parent.get_best_obj(), double_precision);
174 std::cout << std::setw(log_width_problem_rel_gap) << pretty_double(parent.get_relative_gap() * 100, double_precision);
175 std::cout << std::setw(log_width_problem_abs_gap) << pretty_double(parent.get_absolute_gap(), double_precision);
176
177 // Current Node
178 const auto value = t_node.id() == -1 ? t_node.info().best_obj() : t_node.info().best_bound();
179 std::cout << " | ";
180 std::cout << std::setw(log_width_current_node_depth) << t_node.level();
181 std::cout << std::setw(log_width_current_node_status) << t_node.info().status();
182 std::cout << std::setw(log_width_current_node_reason);
183 if (t_node.id() == -1) {
184 std::cout << "H";
185 } else {
186 std::cout << t_node.info().reason();
187 }
188 std::cout << std::setw(log_width_current_node_obj) << pretty_double(value, double_precision);
189 std::cout << " | ";
190
191 std::cout << std::endl;
192
193}
194
195template<class NodeInfoT>
196void idol::Logs::BranchAndBound::Info<NodeInfoT>::Strategy::log_root_node(const Node<NodeInfoT> &t_node) {
197
198 if (!this->parent().get_param_logs()) {
199 return;
200 }
201
202 auto& branch_and_bound = this->parent();
203 const double total_time = branch_and_bound.time().count();
204
205 std::cout
206 << "Root relaxation: objective " << t_node.info().best_bound()
207 << ", " << total_time << " seconds\n"
208 << std::endl;
209
210 if (!m_node_logs) {
211 branch_and_bound.relaxation().optimizer().set_param_logs(false);
212 }
213
214 m_root_node_has_been_printed = true;
215
216}
217
218template<class NodeInfoT>
219void idol::Logs::BranchAndBound::Info<NodeInfoT>::Strategy::log_header() {
220
221 if (m_header_has_been_printed) {
222 return;
223 }
224
225 std::cout << ' ';center(std::cout, "*", table_space, '*') << '\n';
226 std::cout << ' ';center(std::cout, " Branch-and-Bound Algorithm ", table_space) << '\n';
227 std::cout << ' ';center(std::cout, "*", table_space, '*') << '\n';
228
229 std::cout << " | ";
230 center(std::cout, "Algorithm", log_section_width_algorithm);std::cout << " | ";
231 center(std::cout, "Problem", log_section_width_problem);std::cout << " | ";
232 center(std::cout, "Current Node", current_node_space);std::cout << " | ";
233 std::cout << '\n';
234
235 std::cout << " | ";
236 std::cout << std::setw(log_width_explored) << "Expl.";
237 std::cout << std::setw(log_width_open) << "Open";
238 std::cout << std::setw(log_width_total_time) << "Time";
239
240 // Problem
241 std::cout << " | ";
242 std::cout << std::setw(log_width_problem_best_bound) << "Bound";
243 std::cout << std::setw(log_width_problem_best_obj) << "Obj";
244 std::cout << std::setw(log_width_problem_rel_gap) << "Gap (%)";
245 std::cout << std::setw(log_width_problem_abs_gap) << "Abs.";
246
247 // Current Node
248 std::cout << " | ";
249 std::cout << std::setw(log_width_current_node_depth) << "Depth";
250 std::cout << std::setw(log_width_current_node_status) << "Status";
251 std::cout << std::setw(log_width_current_node_reason) << "Reason";
252 std::cout << std::setw(log_width_current_node_obj) << "Value";
253 std::cout << " | ";
254
255 std::cout << std::endl;
256
257 m_header_has_been_printed = true;
258}
259
260template<class NodeInfoT>
261void idol::Logs::BranchAndBound::Info<NodeInfoT>::Strategy::log_after_termination() {
262
263 if (!this->parent().get_param_logs()) {
264 return;
265 }
266
267 Factory<NodeInfoT>::Strategy::Strategy::log_after_termination();
268
269 auto& parent = this->parent();
270
271 std::cout << "Explored " << parent.n_solved_nodes() << " nodes in " << parent.time().count() << " seconds\n" << std::endl;
272
273 const unsigned int n_solutions = parent.get_n_solutions();
274
275 std::cout << "Solution count " << n_solutions << ":";
276
277 for (unsigned int i = 0 ; i < n_solutions ; ++i) {
278 parent.set_solution_index(i);
279 std::cout << '\t' << parent.get_best_obj();
280 }
281
282 parent.set_solution_index(0);
283
284 std::cout << std::endl;
285
286 const auto status = parent.get_status();
287 const auto reason = parent.get_reason();
288
289 switch (reason) {
290 case TimeLimit:
291 std::cout << "Time limit was reached." << std::endl;
292 break;
293 case IterLimit:
294 std::cout << "Iteration limit was reached." << std::endl;
295 break;
296 case ObjLimit:
297 std::cout << "Objective limit was reached." << std::endl;
298 break;
299 case Numerical:
300 std::cout << "There was unrecoverable numerical troubles during the execution of the algorithm." << std::endl;
301 break;
302 case NotSpecified:
303 std::cout << "The reason for termination is not specified." << std::endl;
304 break;
305 default:;
306 }
307
308 switch (status) {
309 case Optimal:
310 std::cout << "Optimal solution found (tolerance " << parent.get_tol_mip_relative_gap() << ")" << std::endl;
311 break;
312 case Feasible:
313 std::cout << "Feasible solution found" << std::endl;
314 break;
315 case Infeasible:
316 std::cout << "No feasible solution found" << std::endl;
317 break;
318 case InfOrUnbnd:
319 std::cout << "Problem is infeasible or unbounded" << std::endl;
320 break;
321 case Unbounded:
322 std::cout << "Problem is unbounded" << std::endl;
323 break;
324 case Fail:
325 std::cout << "There was unrecoverable exceptions." << std::endl;
326 break;
327 case SubOptimal:
328 std::cout << "Sub-optimal solution found" << std::endl;
329 break;
330 default:;
331 }
332
333 std::cout
334 << "Best objective " << pretty_double(parent.get_best_obj(), double_precision)
335 << ", best bound " << pretty_double(parent.get_best_bound(), double_precision)
336 << ", gap " << pretty_double(parent.get_relative_gap() * 100, double_precision) << " %"
337 << std::endl;
338
339}
340
341#endif //IDOL_BRANCHANDBOUND_INFO_H