Sleipnir C++ API
Loading...
Searching...
No Matches
print_diagnostics.hpp
1// Copyright (c) Sleipnir contributors
2
3#pragma once
4
5#include <stddef.h>
6#include <stdint.h>
7
8#include <algorithm>
9#include <array>
10#include <chrono>
11#include <cmath>
12#include <format>
13#include <ranges>
14#include <ratio>
15#include <span>
16#include <string>
17#include <string_view>
18#include <utility>
19
20#include <Eigen/Core>
21#include <gch/small_vector.hpp>
22
23#include "sleipnir/util/print.hpp"
24#include "sleipnir/util/time_tracer.hpp"
25
26namespace slp {
27
29enum class IterationType : uint8_t {
31 NORMAL,
33 SECOND_ORDER_CORRECTION,
35 FEASIBILITY_RESTORATION
36};
37
40template <typename Rep, typename Period = std::ratio<1>>
41constexpr double to_ms(const std::chrono::duration<Rep, Period>& duration) {
42 using std::chrono::duration_cast;
43 using std::chrono::microseconds;
44 return duration_cast<microseconds>(duration).count() / 1e3;
45}
46
51template <typename Scalar>
52std::string power_of_10(Scalar value) {
53 if (value == Scalar(0)) {
54 return " 0";
55 } else {
56 using std::log10;
57 int exponent = static_cast<int>(log10(value));
58
59 if (exponent == 0) {
60 return " 1";
61 } else if (exponent == 1) {
62 return "10";
63 } else {
64 // Gather exponent digits
65 int n = std::abs(exponent);
66 gch::small_vector<int> digits;
67 do {
68 digits.emplace_back(n % 10);
69 n /= 10;
70 } while (n > 0);
71
72 std::string output = "10";
73
74 // Append exponent
75 if (exponent < 0) {
76 output += "⁻";
77 }
78 constexpr std::array strs{"⁰", "¹", "²", "³", "⁴",
79 "⁵", "⁶", "⁷", "⁸", "⁹"};
80 for (const auto& digit : digits | std::views::reverse) {
81 output += strs[digit];
82 }
83
84 return output;
85 }
86 }
87}
88
89#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
95template <typename Scalar>
96void print_too_few_dofs_error(const Eigen::VectorX<Scalar>& c_e) {
97 slp::println("The problem has too few degrees of freedom.");
98 slp::println("Violated constraints (cₑ(x) = 0) in order of declaration:");
99 for (int row = 0; row < c_e.rows(); ++row) {
100 if (c_e[row] < Scalar(0)) {
101 slp::println(" {}/{}: {} = 0", row + 1, c_e.rows(), c_e[row]);
102 }
103 }
104}
105#else
106#define print_too_few_dofs_error(...)
107#endif
108
109#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
117template <typename Scalar>
118void print_c_e_local_infeasibility_error(const Eigen::VectorX<Scalar>& c_e,
119 Scalar tolerance) {
120 using std::abs;
121
122 slp::println(
123 "The problem is locally infeasible due to violated equality "
124 "constraints.");
125 slp::println("Violated constraints (cₑ(x) = 0) in order of declaration:");
126 for (int row = 0; row < c_e.rows(); ++row) {
127 if (abs(c_e[row]) > tolerance) {
128 slp::println(" {}/{}: {} = 0", row + 1, c_e.rows(), c_e[row]);
129 }
130 }
131}
132#else
133#define print_c_e_local_infeasibility_error(...)
134#endif
135
136#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
144template <typename Scalar>
145void print_c_i_local_infeasibility_error(const Eigen::VectorX<Scalar>& c_i,
146 Scalar tolerance) {
147 slp::println(
148 "The problem is locally infeasible due to violated inequality "
149 "constraints.");
150 slp::println("Violated constraints (cᵢ(x) ≥ 0) in order of declaration:");
151 for (int row = 0; row < c_i.rows(); ++row) {
152 if (c_i[row] < -tolerance) {
153 slp::println(" {}/{}: {} ≥ 0", row + 1, c_i.rows(), c_i[row]);
154 }
155 }
156}
157#else
158#define print_c_i_local_infeasibility_error(...)
159#endif
160
161#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
162inline void print_bound_constraint_global_infeasibility_error(
163 const std::span<const std::pair<Eigen::Index, Eigen::Index>>
164 conflicting_lower_upper_bound_indices) {
165 slp::println(
166 "The problem is globally infeasible due to conflicting bound "
167 "constraints:");
168 for (const auto& [lower_bound_idx, upper_bound_idx] :
169 conflicting_lower_upper_bound_indices) {
170 slp::println(
171 " Inequality constraint {} gives a lower bound that is greater than "
172 "the upper bound given by inequality constraint {}",
173 lower_bound_idx, upper_bound_idx);
174 }
175}
176#else
177#define print_bound_constraint_global_infeasibility_error(...)
178#endif
179
180#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
189template <typename Scalar>
190void print_initial_iterate_diagnostics(Scalar error, Scalar cost,
191 Scalar infeasibility,
192 Scalar complementarity, Scalar μ) {
193 slp::println("┏{:━^119}┓", "");
194 slp::println(
195 "┃{:^4} {:^9} {:^10} {:^11} {:^10} {:^8} {:^8} {:^5} {:^5} {:^8} {:^8} "
196 "{:^8} {:^8} {:^2}┃",
197 "iter", "duration", "error", "cost", "infeas.", "complem.", "μ", "δ", "γ",
198 "|p_pr|", "|p_du|", "α", "α_z", "↩");
199 slp::println("┡{:━^119}┩", "");
200
201 slp::println(
202 "│init {:^9} {:10.4e} {:11.4e} {:10.4e} {:8.2e} {:8.2e} {:^50}│", "",
203 error, cost, infeasibility, complementarity, μ, "");
204}
205#else
206#define print_initial_iterate_diagnostics(...)
207#endif
208
209#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
230template <typename Scalar, typename Rep, typename Period = std::ratio<1>>
231void print_iterate_diagnostics(int iterations, IterationType type,
232 const std::chrono::duration<Rep, Period>& time,
233 Scalar error, Scalar cost, Scalar infeasibility,
234 Scalar complementarity, Scalar μ, Scalar δ,
235 Scalar γ, Scalar full_primal_step_inf_norm,
236 Scalar full_dual_step_inf_norm, Scalar α,
237 Scalar α_max, Scalar α_reduction_factor,
238 Scalar α_z) {
239 if (iterations > 0 && iterations % 20 == 0) {
240 slp::println("┢{:━^119}┪", "");
241 slp::println(
242 "┃{:^4} {:^9} {:^10} {:^11} {:^10} {:^8} {:^8} {:^5} {:^5} {:^8} "
243 "{:^8} {:^8} {:^8} {:^2}┃",
244 "iter", "duration", "error", "cost", "infeas.", "complem.", "μ", "δ",
245 "γ", "|p_pr|", "|p_du|", "α", "α_z", "↩");
246 slp::println("┡{:━^119}┩", "");
247 }
248
249 // For the number of backtracks, we want x such that:
250 //
251 // αᵐᵃˣrˣ = α
252 //
253 // where r ∈ (0, 1) is the reduction factor.
254 //
255 // rˣ = α/αᵐᵃˣ
256 // ln(rˣ) = ln(α/αᵐᵃˣ)
257 // x ln(r) = ln(α/αᵐᵃˣ)
258 // x = ln(α/αᵐᵃˣ)/ln(r)
259 using std::log;
260 int backtracks = static_cast<int>(log(α / α_max) / log(α_reduction_factor));
261
262 constexpr std::array ITERATION_TYPES{" ", "s", "r"};
263 slp::println(
264 "│{:4} {:1} {:9.3f} {:10.4e} {:11.4e} {:10.4e} {:8.2e} {:8.2e} {:<5} "
265 "{:<5} {:8.2e} {:8.2e} {:8.2e} {:8.2e} {:2d}│",
266 iterations, ITERATION_TYPES[std::to_underlying(type)], to_ms(time), error,
267 cost, infeasibility, complementarity, μ, power_of_10(δ), power_of_10(γ),
268 full_primal_step_inf_norm, full_dual_step_inf_norm, α, α_z, backtracks);
269}
270#else
271#define print_iterate_diagnostics(...)
272#endif
273
280 public:
290 template <typename Scalar>
291 IterateDiagnosticsTable(bool print, [[maybe_unused]] Scalar error,
292 [[maybe_unused]] Scalar cost,
293 [[maybe_unused]] Scalar infeasibility,
294 [[maybe_unused]] Scalar complementarity,
295 [[maybe_unused]] Scalar μ)
296 : m_print{print} {
297 if (m_print) {
298 print_initial_iterate_diagnostics(error, cost, infeasibility,
299 complementarity, μ);
300 }
301 }
302
305 if (m_print) {
306 slp::println("└{:─^119}┘", "");
307 }
308 }
309
311 IterateDiagnosticsTable& operator=(const IterateDiagnosticsTable&) = delete;
312
313 private:
314 bool m_print;
315};
316
321template <int Width>
322 requires(Width > 0)
323std::string histogram(double value) {
324 value = std::clamp(value, 0.0, 1.0);
325
326 double ipart;
327 int fpart = static_cast<int>(std::modf(value * Width, &ipart) * 8);
328
329 constexpr std::array strs{" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"};
330 std::string hist;
331
332 int index = 0;
333 while (index < ipart) {
334 hist += strs[8];
335 ++index;
336 }
337 if (fpart > 0) {
338 hist += strs[fpart];
339 ++index;
340 }
341 while (index < Width) {
342 hist += strs[0];
343 ++index;
344 }
345
346 return hist;
347}
348
349#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
355inline std::string time_trace_label(std::string_view name, int depth) {
356 if (depth == 0) {
357 return std::string{name};
358 }
359 return std::format("{:{}}↳ {}", "", 2 * (depth - 1), name);
360}
361#endif
362
363#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
368template <const auto& Entries>
369 requires(detail::is_valid_time_trace(Entries))
370void print_solver_diagnostics(
371 const TimeTracerTree<SolveTimeTracer, Entries>& solve_tracers) {
372 auto entries = solve_tracers.entries();
373 auto tracers = solve_tracers.tracers();
374 auto solve_duration = to_ms(tracers[0].total_duration());
375
376 slp::println("┏{:━^66}┓", "");
377 slp::println("┃{:^21} {:^18} {:^10} {:^9} {:^4}┃", "time trace", "percentage",
378 "total", "each", "runs");
379 slp::println("┡{:━^66}┩", "");
380
381 for (size_t i = 0; i < tracers.size(); ++i) {
382 const auto& tracer = tracers[i];
383 double norm = solve_duration == 0.0
384 ? (i == 0 ? 1.0 : 0.0)
385 : to_ms(tracer.total_duration()) / solve_duration;
386 slp::println("│{:<21} {:>6.2f}%▕{}▏ {:>10.3f} {:>9.3f} {:>4}│",
387 time_trace_label(entries[i].name, entries[i].depth),
388 norm * 100.0, histogram<9>(norm),
389 to_ms(tracer.total_duration()),
390 to_ms(tracer.average_duration()), tracer.num_solves());
391 }
392
393 slp::println("└{:─^66}┘", "");
394}
395#else
396#define print_solver_diagnostics(...)
397#endif
398
399#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
404template <const auto& Entries>
405 requires(detail::is_valid_time_trace(Entries))
406void print_setup_diagnostics(
407 const TimeTracerTree<SetupTimeTracer, Entries>& setup_tracers) {
408 auto entries = setup_tracers.entries();
409 auto tracers = setup_tracers.tracers();
410 auto setup_duration = to_ms(tracers[0].duration());
411
412 // Print link to diagnostic output description
413 slp::println(
414 "See https://sleipnirgroup.github.io/Sleipnir/md_usage.html#output for "
415 "diagnostic output description.\n");
416
417 // Print heading
418 slp::println("┏{:━^50}┓", "");
419 slp::println("┃{:^21} {:^18} {:^9}┃", "time trace", "percentage", "duration");
420 slp::println("┡{:━^50}┩", "");
421
422 // Print setup tracers
423 for (size_t i = 0; i < tracers.size(); ++i) {
424 const auto& tracer = tracers[i];
425 double norm = setup_duration == 0.0
426 ? (i == 0 ? 1.0 : 0.0)
427 : to_ms(tracer.duration()) / setup_duration;
428 slp::println("│{:<21} {:>6.2f}%▕{}▏ {:>9.3f}│",
429 time_trace_label(entries[i].name, entries[i].depth),
430 norm * 100.0, histogram<9>(norm), to_ms(tracer.duration()));
431 }
432
433 slp::println("└{:─^50}┘", "");
434}
435#else
436#define print_setup_diagnostics(...)
437#endif
438
445template <const auto& Entries>
446 requires(detail::is_valid_time_trace(Entries))
448 public:
454 SolveDiagnostics(bool trace, bool print)
455 : m_tracers{trace}, m_print{trace && print} {
456 m_tracers.root().start();
457 }
458
462 m_tracers.root().stop();
463
464 if (m_print) {
465 print_solver_diagnostics(m_tracers);
466 }
467 }
468
469 SolveDiagnostics(const SolveDiagnostics&) = delete;
470 SolveDiagnostics& operator=(const SolveDiagnostics&) = delete;
471
478 return m_tracers[index];
479 }
480
481 private:
483 bool m_print;
484};
485
486} // namespace slp
Definition print_diagnostics.hpp:279
~IterateDiagnosticsTable()
Prints the table bottom if requested.
Definition print_diagnostics.hpp:304
IterateDiagnosticsTable(bool print, Scalar error, Scalar cost, Scalar infeasibility, Scalar complementarity, Scalar μ)
Definition print_diagnostics.hpp:291
Definition print_diagnostics.hpp:447
SolveDiagnostics(bool trace, bool print)
Definition print_diagnostics.hpp:454
~SolveDiagnostics()
Definition print_diagnostics.hpp:461
SolveTimeTracer & operator[](TimeTracerIndex< Entries > index)
Definition print_diagnostics.hpp:477
Definition time_tracer.hpp:64
Definition time_tracer.hpp:321
Definition time_tracer.hpp:360