Sleipnir C++ API
Loading...
Searching...
No Matches
print_diagnostics.hpp
1// Copyright (c) Sleipnir contributors
2
3#pragma once
4
5#include <stdint.h>
6
7#include <algorithm>
8#include <array>
9#include <chrono>
10#include <cmath>
11#include <ranges>
12#include <ratio>
13#include <span>
14#include <string>
15#include <utility>
16
17#include <Eigen/Core>
18#include <gch/small_vector.hpp>
19
20#include "sleipnir/util/print.hpp"
21#include "sleipnir/util/profiler.hpp"
22
23namespace slp {
24
26enum class IterationType : uint8_t {
28 NORMAL,
30 SECOND_ORDER_CORRECTION,
32 FEASIBILITY_RESTORATION
33};
34
37template <typename Rep, typename Period = std::ratio<1>>
38constexpr double to_ms(const std::chrono::duration<Rep, Period>& duration) {
39 using std::chrono::duration_cast;
40 using std::chrono::microseconds;
41 return duration_cast<microseconds>(duration).count() / 1e3;
42}
43
48template <typename Scalar>
49std::string power_of_10(Scalar value) {
50 if (value == Scalar(0)) {
51 return " 0";
52 } else {
53 using std::log10;
54 int exponent = static_cast<int>(log10(value));
55
56 if (exponent == 0) {
57 return " 1";
58 } else if (exponent == 1) {
59 return "10";
60 } else {
61 // Gather exponent digits
62 int n = std::abs(exponent);
63 gch::small_vector<int> digits;
64 do {
65 digits.emplace_back(n % 10);
66 n /= 10;
67 } while (n > 0);
68
69 std::string output = "10";
70
71 // Append exponent
72 if (exponent < 0) {
73 output += "⁻";
74 }
75 constexpr std::array strs{"⁰", "¹", "²", "³", "⁴",
76 "⁵", "⁶", "⁷", "⁸", "⁹"};
77 for (const auto& digit : digits | std::views::reverse) {
78 output += strs[digit];
79 }
80
81 return output;
82 }
83 }
84}
85
86#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
92template <typename Scalar>
93void print_too_few_dofs_error(
94 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_e) {
95 slp::println("The problem has too few degrees of freedom.");
96 slp::println("Violated constraints (cₑ(x) = 0) in order of declaration:");
97 for (int row = 0; row < c_e.rows(); ++row) {
98 if (c_e[row] < Scalar(0)) {
99 slp::println(" {}/{}: {} = 0", row + 1, c_e.rows(), c_e[row]);
100 }
101 }
102}
103#else
104#define print_too_few_dofs_error(...)
105#endif
106
107#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
115template <typename Scalar>
116void print_c_e_local_infeasibility_error(
117 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_e, Scalar tolerance) {
118 using std::abs;
119
120 slp::println(
121 "The problem is locally infeasible due to violated equality "
122 "constraints.");
123 slp::println("Violated constraints (cₑ(x) = 0) in order of declaration:");
124 for (int row = 0; row < c_e.rows(); ++row) {
125 if (abs(c_e[row]) > tolerance) {
126 slp::println(" {}/{}: {} = 0", row + 1, c_e.rows(), c_e[row]);
127 }
128 }
129}
130#else
131#define print_c_e_local_infeasibility_error(...)
132#endif
133
134#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
142template <typename Scalar>
143void print_c_i_local_infeasibility_error(
144 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_i, Scalar tolerance) {
145 slp::println(
146 "The problem is locally infeasible due to violated inequality "
147 "constraints.");
148 slp::println("Violated constraints (cᵢ(x) ≥ 0) in order of declaration:");
149 for (int row = 0; row < c_i.rows(); ++row) {
150 if (c_i[row] < -tolerance) {
151 slp::println(" {}/{}: {} ≥ 0", row + 1, c_i.rows(), c_i[row]);
152 }
153 }
154}
155#else
156#define print_c_i_local_infeasibility_error(...)
157#endif
158
159#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
160inline void print_bound_constraint_global_infeasibility_error(
161 const std::span<const std::pair<Eigen::Index, Eigen::Index>>
162 conflicting_lower_upper_bound_indices) {
163 slp::println(
164 "The problem is globally infeasible due to conflicting bound "
165 "constraints:");
166 for (const auto& [lower_bound_idx, upper_bound_idx] :
167 conflicting_lower_upper_bound_indices) {
168 slp::println(
169 " Inequality constraint {} gives a lower bound that is greater than "
170 "the upper bound given by inequality constraint {}",
171 lower_bound_idx, upper_bound_idx);
172 }
173}
174#else
175#define print_bound_constraint_global_infeasibility_error(...)
176#endif
177
178#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
187template <typename Scalar>
188void print_initial_iterate_diagnostics(Scalar error, Scalar cost,
189 Scalar infeasibility,
190 Scalar complementarity, Scalar μ) {
191 slp::println("┏{:━^119}┓", "");
192 slp::println(
193 "┃{:^4} {:^9} {:^10} {:^11} {:^10} {:^8} {:^8} {:^5} {:^5} {:^8} {:^8} "
194 "{:^8} {:^8} {:^2}┃",
195 "iter", "duration", "error", "cost", "infeas.", "complem.", "μ", "δ", "γ",
196 "|p_pr|", "|p_du|", "α_pr", "α_du", "↩");
197 slp::println("┡{:━^119}┩", "");
198
199 slp::println(
200 "│init {:^9} {:10.4e} {:11.4e} {:10.4e} {:8.2e} {:8.2e} {:^50}│", "",
201 error, cost, infeasibility, complementarity, μ, "");
202}
203#else
204#define print_initial_iterate_diagnostics(...)
205#endif
206
207#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
228template <typename Scalar, typename Rep, typename Period = std::ratio<1>>
229void print_iteration_diagnostics(int iterations, IterationType type,
230 const std::chrono::duration<Rep, Period>& time,
231 Scalar error, Scalar cost,
232 Scalar infeasibility, Scalar complementarity,
233 Scalar μ, Scalar δ, Scalar γ,
234 Scalar full_primal_step_inf_norm,
235 Scalar full_dual_step_inf_norm,
236 Scalar primal_α, Scalar primal_α_max,
237 Scalar α_reduction_factor, Scalar dual_α) {
238 if (iterations > 0 && iterations % 20 == 0) {
239 slp::println("┢{:━^119}┪", "");
240 slp::println(
241 "┃{:^4} {:^9} {:^10} {:^11} {:^10} {:^8} {:^8} {:^5} {:^5} {:^8} "
242 "{:^8} {:^8} {:^8} {:^2}┃",
243 "iter", "duration", "error", "cost", "infeas.", "complem.", "μ", "δ",
244 "γ", "|p_pr|", "|p_du|", "α_pr", "α_du", "↩");
245 slp::println("┡{:━^119}┩", "");
246 }
247
248 // For the number of backtracks, we want x such that:
249 //
250 // αᵐᵃˣrˣ = α
251 //
252 // where r ∈ (0, 1) is the reduction factor.
253 //
254 // rˣ = α/αᵐᵃˣ
255 // ln(rˣ) = ln(α/αᵐᵃˣ)
256 // x ln(r) = ln(α/αᵐᵃˣ)
257 // x = ln(α/αᵐᵃˣ)/ln(r)
258 using std::log;
259 int backtracks =
260 static_cast<int>(log(primal_α / primal_α_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, primal_α, dual_α,
269 backtracks);
270}
271#else
272#define print_iteration_diagnostics(...)
273#endif
274
275#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
277inline void print_bottom_iteration_diagnostics() {
278 slp::println("└{:─^119}┘", "");
279}
280#else
281#define print_bottom_iteration_diagnostics(...)
282#endif
283
288template <int Width>
289 requires(Width > 0)
290std::string histogram(double value) {
291 value = std::clamp(value, 0.0, 1.0);
292
293 double ipart;
294 int fpart = static_cast<int>(std::modf(value * Width, &ipart) * 8);
295
296 constexpr std::array strs{" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"};
297 std::string hist;
298
299 int index = 0;
300 while (index < ipart) {
301 hist += strs[8];
302 ++index;
303 }
304 if (fpart > 0) {
305 hist += strs[fpart];
306 ++index;
307 }
308 while (index < Width) {
309 hist += strs[0];
310 ++index;
311 }
312
313 return hist;
314}
315
316#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
320inline void print_solver_diagnostics(
321 const gch::small_vector<SolveProfiler>& solve_profilers) {
322 auto solve_duration = to_ms(solve_profilers[0].total_duration());
323
324 slp::println("┏{:━^66}┓", "");
325 slp::println("┃{:^21} {:^18} {:^10} {:^9} {:^4}┃", "time trace", "percentage",
326 "total", "each", "runs");
327 slp::println("┡{:━^66}┩", "");
328
329 for (auto& profiler : solve_profilers) {
330 double norm = solve_duration == 0.0
331 ? (&profiler == &solve_profilers[0] ? 1.0 : 0.0)
332 : to_ms(profiler.total_duration()) / solve_duration;
333 slp::println("│{:<21} {:>6.2f}%▕{}▏ {:>10.3f} {:>9.3f} {:>4}│",
334 profiler.name(), norm * 100.0, histogram<9>(norm),
335 to_ms(profiler.total_duration()),
336 to_ms(profiler.average_duration()), profiler.num_solves());
337 }
338
339 slp::println("└{:─^66}┘", "");
340}
341#else
342#define print_solver_diagnostics(...)
343#endif
344
345#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
349inline void print_setup_diagnostics(
350 const gch::small_vector<SetupProfiler>& setup_profilers) {
351 auto setup_duration = to_ms(setup_profilers[0].duration());
352
353 // Print link to diagnostic output description
354 slp::println(
355 "See https://sleipnirgroup.github.io/Sleipnir/md_usage.html#output for "
356 "diagnostic output description.\n");
357
358 // Print heading
359 slp::println("┏{:━^50}┓", "");
360 slp::println("┃{:^21} {:^18} {:^9}┃", "time trace", "percentage", "duration");
361 slp::println("┡{:━^50}┩", "");
362
363 // Print setup profilers
364 for (auto& profiler : setup_profilers) {
365 double norm = setup_duration == 0.0
366 ? (&profiler == &setup_profilers[0] ? 1.0 : 0.0)
367 : to_ms(profiler.duration()) / setup_duration;
368 slp::println("│{:<21} {:>6.2f}%▕{}▏ {:>9.3f}│", profiler.name(),
369 norm * 100.0, histogram<9>(norm), to_ms(profiler.duration()));
370 }
371
372 slp::println("└{:─^50}┘", "");
373}
374#else
375#define print_setup_diagnostics(...)
376#endif
377
378} // namespace slp